안녕하세요
오늘 포스팅 주제는 자바스크립트의 Promise 패턴입니다!
지난 포스팅에서는 비동기 작업을 위해 콜백 함수를 사용한다고 했었습니다.
하지만 콜백 함수는 가독성 등의 단점이 있기 때문에 여러 개선 방법이 있다고 설명했죠
그 중 하나가 프라미스(Promise) 패턴입니다!
[예제는 Node.js 환경에서 테스트할 예정이구요 Node.js 뿐만 아니라 웹 브라우저에서 사용하셔도 동일합니다!]
먼저 설명 전 이전 시간에 진행했던 콜백 함수의 코드를 프라미스 패턴으로 동일하게 변경해보도록 하겠습니다!
(콜백 함수사용 패턴)
(프라미스 패턴)
(프라미스패턴 코드 실행 결과, 콜백함수와 동일)
코드를 비교해보면 많이 바뀌었습니다.
하지만 기능은 동일하죠
더 어려워지고, 복잡해지고, 불편해보이실지는 몰라도 사용해보면 엄청나게 간결하고 이만큼 편리한것이 없습니다!
마지막 예제에서 중첩된 콜백함수 패턴을 프라미스로 구현한 코드를 보며 한번 확인해볼 예정입니다
처음이라 낮설고 어려울수도 있지만 이 프라미스 사용법을 잘 숙지하신다면 정말 큰 도움이 될 것 입니다
자바스크립트의 프라미스는 아래와 같이 4가지 상태를 나타냅니다.
fulfilled(처리 완료) - 작업 성공
rejected(거부 됨) - 작업 실패
pending(보류 됨) - 작업 시작 전
settled(해결 됨) - 처리 완료 혹은 거부 됨
위와 같은 사이클로 동작합니다.
(1)
그림을 왼쪽부터 살펴보면 Promise가 하나 있고 Pending 상태입니다.
아직 작업을 시작하지 않은 대기상태(보류)를 나타냅니다.
(2-1)
작업을 진행 하다가 fulfill 혹은 reject 상황으로 상태가 바뀝니다.
먼저 성공했다고 가정하면 fulfill 상태가 되고 그 다음 과정인 then()의 콜백 함수가 호출됩니다.
성공이 되었으니 settled 상태가 되죠
then 의 콜백 함수 내에서 비동기 작업이 수행된 이후 수행할 코드를 작성하시면 됩니다.
(2-2)
reject(거부) 상태로 되었다면 작업을 진행하던 도중 오류가 발행했거나 문제가 있어서 거부가 된것입니다.
그렇게되면 then이 아닌 catch()의 콜백 함수가 호출되구요 이 콜백 함수 내에서 에러 핸들링 코드를 작성하시면 됩니다.
(3)
사실 프라미스가 1개하면 위의 2번 단계가 끝인데요
만약 프라미스가 여러개 생성되어있고(a, b, c) 순차적으로 비동기작업을 진행해야 한다면
a가 끝난 후 b 프라미스를 return 해줍니다.
b 프라미스는 a와 동일하게 fulfill 혹은 reject 상태가 되고 성공, 실패시 코드를 실행하게 되죠
프라미스가 체이닝(연결)되면 하나하나 순차적으로 실행하게 됩니다.
아직 어려우실 수 있는데 예제를 확인해보며 하나씩 살펴봅시다.
아까 위에서 미리 소개한 코드입니다.
1행의 var loading 변수에는 함수가 저장되어있습니다.
해당 함수는 path라는 데이터를 매개변수로 가지고 있고
함수 실행 시 새로운 프라미스(Promise)객체를 생성하고 반환합니다. (2행)
프라미스를 새로 생성할 때(new Promise()) 꼭 2개의 매개변수를 갖는 콜백함수를 함께 전달해야합니다.
(2행에서 function(resolve, reject) { ... })
프라미스 내부에서 성공을 하면 resolve를 호출하고 실패하면 reject를 호출하기 때문이죠
(resolve, reject 대신 다른 원하는 이름으로 하셔도 되지만 서로 이해하기 쉽게 하기위해선 resolve, reject로 정의하시는게 좋습니다)
프라미스가 생성만 되었지 아직 작업이 진행되지 않았기 때문에 현재 Pending 상태입니다.
아래 8행에서 loading()을 호출하며 path 매개변수로 '/folder/text/' 문자열을 인자로 전달하고 있습니다.
자세히 보면 loading().then().catch() 형식으로 이어져 있습니다.
만약 프라미스에서 작업 도중 성공하면 then 함수의 콜백 함수가 실행이 되고
실패하게 되면 catch 함수의 콜백 함수가 실행 됩니다.
코드의 실행 순서를 천천히 확인해 보면 먼저 8행에서 '/folder/text/' 문자열을 전달하고 프라미스에서 작업을 진행합니다.
(2행에서 생성한 프라미스)
프라미스 안에는 먼저 path를 콘솔로 출력을 합니다(3행)
그리고 resolve(작업 성공)를 호출하며 path + 'sample.txt' 의 값을 전달해줍니다.(4행)
작업이 성공되었으니 then의 콜백 함수가 호출됩니다.
resolve(결과값)은 then의 콜백함수의 매개변수로 전달됩니다!
9행에서 성공 결과를 콘솔에 출력하는 모습입니다.
만약 에러가 발생되어 프라미스 내에서 resolve대신 reject가 호출되었다면 then말고 catch의 콜백 함수가 실행되겠죠?
위 예제 코드에는 reject(거부, 실패 됨)를 사용하지 않았습니다.
만약 작업 도중 오류가 있거나 특정 조건에서 실패를 원하시면 아래와 같이 구현하시면 됩니다.
5행의 조건문에서 전달받은 path가 '/system/' 일 경우
reject를 호출하고 있습니다.
호출하면서 에러 메시지를 함께 전달하고 있죠
이렇게 할 경우 reject가 호출되면 15행의 catch로 이어서 진행됩니다.
catch의 콜백함수로 에러 메시지도 함께 전달이 되면서 16행에서 출력이 됩니다.
전달된 path가 '/system/' 인 경우 아래와 같이 정상적으로 실행이 됩니다.
지금까지는 간단한 예제를 살펴보았는데
아직까지는 콜백함수 패턴보다 복잡해보일수도 있습니다.
종종 설명할 때 예로 들었던 a, b, c 비동기 작업을 차례대로 처리하는 코드를 보며
콜백함수 패턴과 프라미스 패턴을 서로 비교해봅시다!
[콜백 함수 패턴 및 실행 결과]
[프라미스 패턴 및 실행 결과]
(결과는 완전히 동일합니다)
로그를 하나씩 차례대로 남기는 예제입니다.
콜백함수 패턴은 가독성이 떨어져서 보기 힘든 반면에 프라미스 패턴은
프라미스의 기능만 분석하면 아래 8행부터 18행 까지의 실제 실행 결과를 금방 유추할 수 있습니다.
무엇보다 then()을 통해 프라미스 체이닝(Promise chaining)되어있어서
실행 순서를 쉽게 파악이 가능하고 가독성도 훨씬 깔끔합니다.
또한 then에서 중간에 오류가 발생(reject)하면 아래에 있는 then은 모두 건너 뛰고 바로 16행의 catch로 이동합니다!
그리고 then 하나 catch 하나 이렇게 쌍을 이루고 각각의 작업마다 에러를 핸들링 할수도 있습니다.
위와같이 then 다음에 catch를 바로 이어서
해당 작업의 오류를 바로 아래에서 핸들링이 가능합니다.
(위 예제는 catch에서 콘솔 출력만 하는데 catch에서 핸들링 후 프라미스 객체를 리턴하면 다시 이어서 진행이 가능합니다)
여러 프라미스를 한번에 묶어서 작업을 진행하는 Promise.all() 함수도 존재합니다.
해당 내용은 본 포스팅에서 다루지 않겠습니다.
프라미스에 대해 더 많은 정보를 알고싶으시면 구글에 검색하거나 아래 링크를 참고하시면 큰 도움이 됩니다!
https://developers.google.com/web/fundamentals/primers/promises?hl=ko
다음 시간에는 프라미스를 더 깨끗하게 사용할 수 있는
비동기 함수에 대해 알아보도록 하겠습니다
(async/await)
감사합니다.