async function
async function (비동기 함수 선언)
async function 선언은 주어진 이름에 새로운 async 함수 바인딩을 만듭니다. 함수 본문 안에서 await 키워드를 사용할 수 있어, 비동기적이고 promise 기반인 동작을 더 깔끔한 스타일로 작성하고 promise 체인을 명시적으로 구성할 필요를 줄여 줍니다. 원문은 MDN의 JavaScript 참고 문서입니다.
본문
async function 선언은 주어진 이름에 새로운 async 함수 바인딩을 만듭니다. 함수 본문 내에서는 await 키워드가 허용되어, promise 기반의 비동기 동작을 더 깔끔한 스타일로 작성할 수 있고 promise 체인을 명시적으로 구성할 필요가 없어집니다. async 함수 표현식(async function expression)으로도 async 함수를 정의할 수 있습니다.
다음은 "Try it" 예제로, 2초 후 resolve되는 promise를 만든 뒤 await로 기다리는 형태입니다.
function resolveAfter2Seconds() {
return new Promise((resolve) => {
setTimeout(() => {
resolve("resolved");
}, 2000);
});
}
async function asyncCall() {
console.log("calling");
const result = await resolveAfter2Seconds();
console.log(result);
// Expected output: "resolved"
}
asyncCall();
문법 (Syntax)
async function name(param0) {
statements
}
async function name(param0, param1) {
statements
}
async function name(param0, param1, /* …, */ paramN) {
statements
}
name: 함수의 이름입니다.param(선택): 함수의 형식 매개변수 이름입니다. 매개변수 문법은 Functions 참고 문서를 참고하세요.statements(선택): 함수 본문을 구성하는 문들입니다.await메커니즘을 사용할 수 있습니다.
참고: async와 function 사이에 줄바꿈(라인 터미네이터)이 있으면 안 됩니다. 그렇지 않으면 자동으로 세미콜론이 삽입되어 async는 식별자가 되고 나머지는 함수 선언이 되어 버립니다.
설명 (Description)
async 함수 선언은 AsyncFunction 객체를 만듭니다. async 함수가 호출될 때마다 새로운 Promise를 반환하며, 이 Promise는 async 함수가 반환한 값으로 resolve되거나, async 함수 내부에서 잡히지 않은 예외로 reject됩니다.
async 함수는 0개 이상의 await 표현식을 포함할 수 있습니다. await 표현식은 반환된 promise가 fulfilled 또는 rejected 될 때까지 실행을 일시 중단함으로써, promise를 반환하는 함수가 마치 동기적으로 동작하는 것처럼 보이게 합니다. promise의 resolve된 값은 await 표현식의 반환 값으로 취급됩니다. async/await를 사용하면 비동기 코드 주변에 일반적인 try/catch 블록을 사용할 수 있습니다.
참고: await 키워드는 일반 JavaScript 코드에서 async 함수 내부에서만 유효합니다. async 함수 본문 밖에서 사용하면 SyntaxError가 발생합니다. 다만 await는 JavaScript 모듈에서는 단독으로도 사용할 수 있습니다.
참고: async/await의 목적은 promise 기반 API를 사용하기 위해 필요한 문법을 단순화하는 것입니다. async/await의 동작은 생성기(generator)와 promise를 결합한 것과 유사합니다.
async 함수는 항상 promise를 반환합니다. async 함수의 반환 값이 명시적으로 promise가 아니라면 암시적으로 promise로 감싸집니다. 예를 들어 다음 코드는
async function foo() {
return 1;
}
다음과 유사합니다.
function foo() {
return Promise.resolve(1);
}
다만 async 함수의 반환 값은 Promise.resolve로 감싼 것처럼 동작하지만 완전히 동등하지는 않습니다. async 함수는 다른 참조(reference)를 반환하는 반면, Promise.resolve는 주어진 값이 promise라면 같은 참조를 반환합니다. 이는 promise와 async 함수 반환 값의 동등성(equality)을 확인하려 할 때 문제가 될 수 있습니다.
const p = new Promise((res, rej) => { res(1); });
async function asyncReturn() { return p; }
function basicReturn() { return Promise.resolve(p); }
console.log(p === basicReturn()); // true
console.log(p === asyncReturn()); // false
async 함수의 본문은 0개 이상의 await 표현식으로 나뉘어 있다고 생각할 수 있습니다. 첫 번째 await 표현식(있다면)까지의 최상위 코드는 동기적으로 실행됩니다. 이렇게 하면 await 표현식이 없는 async 함수는 동기적으로 실행됩니다. 그러나 함수 본문 안에 await 표현식이 있다면 async 함수는 항상 비동기적으로 완료됩니다.
예를 들어:
async function foo() {
await 1;
}
이는 다음 코드와도 동등합니다.
function foo() {
return Promise.resolve(1).then(() => undefined);
}
각 await 표현식 이후의 코드는 .then 콜백 안에 존재한다고 생각할 수 있습니다. 이렇게 하여 함수를 통과하는 각 재진입(reentrant) 단계마다 promise 체인이 점진적으로 구성됩니다. 반환 값이 체인의 마지막 연결 고리를 형성합니다.
다음 예제는 두 promise를 차례로 await합니다. foo 함수를 통과하는 진행은 세 단계로 나뉩니다. foo 본문의 첫 줄은 동기적으로 실행되고, await 표현식이 pending promise로 구성됩니다. 그다음 foo를 통한 진행이 일시 중단되고 제어가 foo를 호출한 함수로 되돌아갑니다. 이후 첫 번째 promise가 fulfilled 또는 rejected 되면 제어가 foo로 돌아가고, await 표현식으로부터 첫 번째 promise의 결과가 반환됩니다(여기서 1이 result1에 할당). 다시 두 번째 await 표현식이 평가되고 진행이 일시 중단됩니다. 두 번째 promise가 완료되면 두 번째 await 표현식에서 결과가 반환되어 result2에 할당되고, return 표현식(있을 경우)으로 제어가 이동합니다. 기본 반환 값 undefined가 현재 promise의 resolve 값으로 반환됩니다.
async function foo() {
const result1 = await new Promise((resolve) =>
setTimeout(() => resolve("1")),
);
const result2 = await new Promise((resolve) =>
setTimeout(() => resolve("2")),
);
}
foo();
promise 체인은 한 번에 만들어지는 것이 아니라, async 함수로부터 제어가 양도되고 반환되는 단계마다 조금씩 구성된다는 점에 주의하세요. 그 결과 동시(concurrent) 비동기 작업을 다룰 때는 오류 처리 동작에 유의해야 합니다. 예를 들어 다음 코드에서는 promise 체인에서 더 뒤쪽에 .catch 핸들러가 구성되어 있어도, 처리되지 않은 promise rejection 오류가 발생합니다. p2가 promise 체인에 "연결"되려면 p1에서 제어가 반환된 후여야 하기 때문입니다.
async function foo() {
const p1 = new Promise((resolve) => setTimeout(() => resolve("1"), 1000));
const p2 = new Promise((_, reject) =>
setTimeout(() => reject(new Error("failed")), 500),
);
const results = [await p1, await p2]; // 이렇게 하지 마세요! Promise.all이나 Promise.allSettled를 사용하세요.
}
foo().catch(() => {}); // 모든 오류를 삼키려는 시도
async 함수 선언은 함수 선언과 비슷하게 동작합니다 — 자신의 스코프 최상단으로 호이스팅되어 스코프 어디에서든 호출할 수 있고, 특정 컨텍스트에서만 재선언할 수 있습니다.
예제 (Examples)
async 함수와 실행 순서. 느린 promise와 빠른 promise를 만들어 실행 순서를 관찰합니다.
function resolveAfter2Seconds() {
console.log("starting slow promise");
return new Promise((resolve) => {
setTimeout(() => { resolve("slow"); console.log("slow promise is done"); }, 2000);
});
}
function resolveAfter1Second() {
console.log("starting fast promise");
return new Promise((resolve) => {
setTimeout(() => { resolve("fast"); console.log("fast promise is done"); }, 1000);
});
}
sequentialStart: 느린 promise를 시작하고 await로 대기한 뒤에야 다음(빠른) timer를 시작합니다.sequentialWait: 두 timer를 서로 기다리지 않고 먼저 시작한 뒤, 느린 promise를 await하고 이어서 빠른 promise를 await합니다.concurrent1:Promise.all로 두 timer를 동시에 시작하고 둘 다 완료될 때까지 기다립니다.
사양 및 호환성
async function 선언은 ECMAScript 사양에서 정의됩니다. Baseline "Widely available"로 분류되어 2017년 4월부터 브라우저 전반에서 사용 가능했습니다.
더 알아보기
- async function 표현식
- async function*
- Await
- Promise
- Using promises 가이드