ES6 - Promise & Async/Await
1. 소개
이미지를 가져와서 압축하고 필터를 적용하고 저장하고 싶다고 가정해보자.
가장 먼저 해야 할 일은 편집할 이미지를 가져오는 것이다. getImage 함수가 이를 처리할 수 있다.
이미지가 성공적으로 로드된 후에만 해당 값을 resizeImage 함수에 전달할 수 있다.
이미지 크기가 성공적으로 조정되면 applyFilter 함수에서 이미지에 필터를 적용하려고 할 것이다.
이미지를 압축하고 필터를 추가한 후 이미지를 저장하고 모든 것이 올바르게 작동했음을 사용자에게 알리고 싶다!
결국 우리는 다음과 같은 코드를 얻게 될 것이다.
위 코드를 보면 콜백 함수에 의존하는 많은 중첩 콜백 함수로 코드를 작성했다.
이것은 코드를 읽기 어렵게 만드는 수많은 중첩된 콜백 함수로 끝나기 때문에 종종 콜백 지옥이라고 한다!
다행히도 이제 우리를 도울 수 있는 Promise이라는 것이 생겼다!
Promise이 무엇인지, 이러한 상황에서 어떻게 우리를 도울 수 있는지 살펴보자!
2. Promise Syntax
프로미스는 생성자에 의해 생성된 프로미스 객체는 두 가지의 프로퍼티를 가진다.
바로 state, value 프로퍼티이다.
2 - 1) Promise State : 프로미스는 크게 세 가지 상태를 가진다.
- Pending : 약속이 이행되지도 않았고 그렇다고 거부된 것도 아닌 상태
- Fullfiled : 약속이 이행된 상태
- Rejected : 에러가 있거나 어떤 문제로 인해 약속이 거부된 상태
let promise = new Promise((resolve, reject) => {
//new Promise에 전달되는 함수는 executor(실행 함수) 라고 부른다.
//executor의 인수 resolve와 reject는 자바스크립트가 자체적으로 제공하는 콜백함수다.
//개발자는 resolve와 reject를 신경 쓰지 않고 executor 안 코드만 작성하면 된다.
//다만, resolve나 reject 중 하나는 반드시 호출해야 한다.
//state는 resolve가 호출되면 "fulfilled", reject가 호출되면 "rejected"로 변한다.
//executor는 보통 시간이 걸리는 일을 수행한다 즉 비동기적으로 수행되는 일을 의미.
//일이 끝나면 resolve나 reject 함수를 호출하는데, 이때 프라미스 객체의 상태가 변화합니다.
//이행(resolved)되거나 거부(rejected)된 상태의 프라미스는 ‘처리된(settled)’ 프라미스라고 부릅니다.
//반대되는 프라미스로 '대기(pending)'상태의 프라미스가 있습니다.
resolve('promise')
})
2 - 2) Promise Value : 프로미스의 상태에 따라 Value 값이 달라짐.
즉, promise의 value는 resolve 메서드나 reject 메서드에 인수로 전달하는 값이 된다.
위 예시에서는 'promise'가 value가 될 것이다.
2 - 3) 소비자 메서드
마지막으로, 우리는 전체 promise 객체를 다루지 않고 promise에 의해 해결된 value만을 활용하고 싶을 것이다.
그때 아래 세 가지 메서드를 활용할 수 있다.
- . then : resolve 메서드에 의해 받은 value를 인자로 받는다.
- . catch : reject 메서드에 의해 받은 value를 인자로 받는다.
- . finally : resolve, reject 메서드에 의해 받은 value를 인자로 받는다
.then의 첫 번째 인수는 프라미스가 이행되었을 때 실행되는 함수이고, 여기서 실행 결과를 받는다.
.then의 두 번째 인수는 프라미스가 거부되었을 때 실행되는 함수이고, 여기서 에러를 받는다.
.then
let promise = new Promise(function(resolve, reject) {
setTimeout(() => resolve("done!"), 1000);
});
// resolve 함수는 .then의 첫 번째 함수(인수)를 실행합니다.
promise.then(
result => alert(result), // 1초 후 "done!"을 출력
error => alert(error) // 실행되지 않음
);
//작업이 성공적으로 처리된 경우만 다루고 싶다면 .then에 인수를 하나만 전달하면 됩니다.
let promise = new Promise(resolve => {
setTimeout(() => resolve("done!"), 1000);
});
promise.then(alert); // 1초 뒤 "done!" 출력
. catch
//에러가 발생한 경우만 다루고 싶다면 .then(null, errorHandlingFunction)같이
//null을 첫 번째 인수로 전달하면 됩니다. .catch(errorHandlingFunction)를 써도 되는데,
//.catch는 .then에 null을 전달하는 것과 동일하게 작동합니다.
let promise = new Promise((resolve, reject) => {
setTimeout(() => reject(new Error("에러 발생!")), 1000);
});
// .catch(f)는 promise.then(null, f)과 동일하게 작동합니다
promise.catch(alert); // 1초 뒤 "Error: 에러 발생!" 출력
//.catch(f)는 문법이 간결하다는 점만 빼고 .then(null,f)과 완벽하게 같습니다.
3. Promise Chaining
콜백 지옥 예제에서는 콜백을 실행하기 위해 여러 개의 콜백을 중첩해야 했다.
다행히. then 핸들러가 도움을 줄 수 있다!. then 자체의 결과는 약속 값이다.
이것은 우리가 원하는 만큼 많은. thens를 연결할 수 있음을 의미한다.
이전 then 콜백의 결과가 다음 then 콜백에 대한 인수로 전달되기 때문이다!
어떤가? 콜백 지옥으로 가독성이 떨어진 코드보다 Promise Syntax로 인해 코드가 훨씬 명료해졌음을 알 수 있다.
4. Async/Await
Async/Await 문법은 JavaScript에서 비동기 동작을 추가하고 promise 작업을 더 쉽게 만드는 새로운 방법이다!
async 및 await 키워드를 도입하여 암시 적으로 promise를 반환하는 비동기 함수를 만들 수 있다.
이전에는 new Promise (() => {}), Promise 객체를 사용하여 명시 적으로 promise를 만들 수 있음을 확인했다.
Promise 객체를 명시 적으로 사용하는 대신 이제 암시적으로 객체를 반환하는 비동기 함수를 만들 수 있다!
이것은 우리가 더 이상 Promise 객체를 직접 작성할 필요가 없음을 의미한다.
비동기 함수가 암시적으로 promise를 반환한다는 사실은 꽤 훌륭하지만 await 키워드를 사용하면 비동기 함수의 진정한 힘을 볼 수 있다! await 키워드를 사용하면 awaited 값이 해결된 promise를 반환할 때까지 기다리는 동안 비동기 함수를 일시 중단할 수 있다.
이전에 then () 콜백에서 했던 것처럼 이 해결된 promise의 값을 얻으려면 대기된 promise 값에 변수를 할당할 수 있다!
4 - 1) async 함수
function 앞에 async를 붙이면 해당 함수는 항상 프라미스를 반환한다.
프라미스가 아닌 값을 반환하더라도 이행 상태의 프라미스(resolved promise)로 값을 감싸 이행된 프라미스가 반환되도록 한다.
async function f() {
return 1;
}
f().then(alert); // 1
//명시적으로 프라미스를 반환하는 것도 가능한데, 결과는 동일합니다.
async function f() {
return Promise.resolve(1);
}
f().then(alert); // 1
async가 붙은 함수는 반드시 프라미스를 반환하고, 프라미스가 아닌 것은 프라미스로 감싸 반환한다.
그런데 async가 제공하는 기능은 이뿐만이 아니다.
또 다른 키워드 await는 async 함수 안에서만 동작합니다.
await는 아주 멋진 녀석이다.
자바스크립트는 await 키워드를 만나면 프라미스가 처리(settled)될 때까지 기다린다.
결과는 그 이후 반환된다. 1초 후 이행되는 프라미스를 예시로 사용하여 await가 어떻게 동작하는지 살펴보자.
async function f() {
let promise = new Promise((resolve, reject) => {
setTimeout(() => resolve("완료!"), 1000)
});
let result = await promise; // 프라미스가 이행될 때까지 기다림 (*)
alert(result); // "완료!"
}
f();
함수를 호출하고, 함수 본문이 실행되는 도중에 (*)로 표시한 줄에서 실행이 잠시 '중단’되었다가 프라미스가 처리되면 실행이 재개된다.
이때 프라미스 객체의 result 값이 변수 result에 할당된다. 따라서 위 예시를 실행하면 1초 뒤에 '완료!'가 출력된다.
await는 말 그대로 프라미스가 처리될 때까지 함수 실행을 기다리게 만든다. 프라미스가 처리되면 그 결과와 함께 실행이 재개된다.
프라미스가 처리되길 기다리는 동안엔 엔진이 다른 일을 할 수 있기 때문에, CPU 리소스가 낭비되지 않는다.
await는 promise.then보다 좀 더 세련되게 프라미스의 result 값을 얻을 수 있도록 해주는 문법이다.
promise.then보다 가독성 좋고 쓰기도 쉽다.
* 주의사항 : async 함수가 아닌데 await을 사용하면 문법 에러가 발생한다.
async/await를 함께 사용하면 읽고, 쓰기 쉬운 비동기 코드를 작성할 수 있다.
async/await를 사용하면 promise.then/catch가 거의 필요 없다.
하지만 가끔 가장 바깥 스코프에서 비동기 처리가 필요할 때같이 promise.then/catch를 써야만 하는 경우가 생기기 때문에 async/await가 프라미스를 기반으로 한다는 사실을 알고 있어야 한다.
즉 async/await syntax는 promise syntax의 syntatic sugar라고 할 수 있다!
출처 : ko.javascript.info/async, dev.to/lydiahallie/javascript-visualized-promises-async-await-5gke