자바스크립트의 비동기 처리 → 콜백함수, Promise, Async-Await

나혜수·2023년 2월 9일
0

자바스크립트 실전

목록 보기
8/19

✅비동기처리


동기식 처리 모델 (Synchronous processing model)은 직렬적으로 태스크를 수행한다. 즉, 태스크는 순차적으로 실행되며 어떤 작업이 수행 중이면 다음 태스크는 대기하게 된다. 예를 들어 서버에서 데이터를 가져와서 화면에 표시하는 태스크를 수행할 때, 서버에 데이터를 요청하고 데이터가 응답될 때까지 이후의 태스크들은 블로킹된다.

비동기식 처리 모델 (Asynchronous processing model)은 병렬적으로 태스크를 수행한다. 즉, 태스크가 종료되지 않은 상태라 하더라도 대기하지 않고 즉시 다음 태스크를 실행한다. 예를 들어 서버에서 데이터를 가져와서 화면에 표시하는 태스크를 수행할 때, 서버에 데이터를 요청한 이후 서버로부터 데이터가 응답될 때까지 대기하지 않고 (Non-Blocking) 즉시 다음 태스크를 수행한다. 이후 서버로부터 데이터가 응답되면 이벤트가 발생하고 이벤트 핸들러가 데이터를 가지고 수행할 태스크를 계속해 수행한다.

자바스크립트의 대부분의 DOM 이벤트, Timer 함수(setTimeout, setInterval), Ajax 요청은 비동기식 처리 모델로 동작한다.


✅비동기 처리 예시

setTimeout(), setInterval()

// #1
console.log('Hello');
// #2
setTimeout(function() {
	console.log('Bye');
}, 3000);
// #3
console.log('Hello Again');

setTimeout( ) 함수는 두번째 인자로 들어온 시간만큼 기다린 후에 첫번째 인자로 들어온 콜백 함수를 실행해준다. 따라서 ‘Hello’, ‘Hello Again’를 먼저 출력하고 3초가 지나면 ‘Bye’가 출력된다.

실제 프로젝트에서 DB나 API를 통해 유저 데이터를 얻어오는 경우, 필연적으로 이러한 latency가 발생하게 된다. 이러한 상황을 시뮬레이션 하기 위해 user 로컬 변수를 선언하고, setTimeout( ) 함수를 통해 0.1초 후에 user 변수에 객체를 할당했다.

function findUser(id) {
  let user;
  setTimeout(function () {
    console.log("waited 0.1 sec");
    user = {
      id: id,
      name: "User" + id,
      email: id + "@test.com",
    };
  }, 100);
  return user;
}

const user = findUser(1);
console.log("user:", user);


// user: undefined
// waited 0.1 sec.

setTimeout( )은 비동기 함수 호출이기 때문에 실행이 완료될 때 까지 기다리지 않고 다음 라인인 return user로 넘어가버린다. 따라서 findUser(1)은 undefined를 리턴하게 된다. 0.1초 후에 콜백 함수가 실행되면서 "waited 0.1 sec"가 출력되고, user 로컬 변수에 원하는 객체가 할당되었지만 이미 늦었다.

setTimeout( )과 같은 비동기 함수를 호출하게 되면 함수의 실행이 완료되기 전에 다음 라인이 실행되어 버리기 때문에 각별히 주의해야 한다.

Ajax Web API 요청

비동기 처리의 가장 흔한 사례는 제이쿼리의 ajax 통신이다. 보통 화면에 표시할 이미지나 데이터를 서버에서 불러와 표시해야 하는데, 이때 ajax 통신으로 해당 데이터를 서버로부터 가져올 수 있다.

function getData() {
	var tableData;
	$.get('https://domain.com/products/1', function(response) {
		tableData = response;
	});
	return tableData;
}

console.log(getData()); // undefined

$.get( )이 ajax 통신을 하는 부분이다. 지정된 URL (https://domain.com)에 HTTP GET 요청을 보내 product1 정보를 요청하는 코드이다. tableData = response 코드로 서버에서 받아온 데이터를 tableData라는 변수에 저장한다.

위 코드에서 getData( )를 호출하면 서버에서 받아온 데이터가 아닌 undefined가 반환된다.
그 이유는 $.get( )로 데이터를 요청하고 받아올 때까지 기다려주지 않고 다음 코드인 return tableData를 실행했기 때문이다. 이렇게 특정 로직의 실행이 끝날 때까지 기다려주지 않고 나머지 코드를 먼저 실행하는 것이 비동기 처리이다.

jQuery
제이쿼리는 자바스크립트 언어를 간편하게 사용할 수 있도록 단순화시킨 오픈소스 기반의 자바스크립트 라이브러리이다. 제이쿼리를 사용하면 아주 간편하게 HTML 요소를 선택하고, 선택된 요소에 손쉽게 특정 동작을 설정할 수 있다.

제이쿼리의 기본 문법은 다음과 같다.

$(선택자).동작함수();

$ 기호는 제이쿼리를 의미하고, 제이쿼리에 접근할 수 있게 해주는 식별자다.
선택자를 이용하여 원하는 HTML 요소를 선택하고, 동작 함수를 정의하여 선택된 요소에 원하는 동작을 설정한다.

Ajax
Ajax란 Asynchronous JavaScript and XML의 약자로, 자바스크립트를 이용해 비동기식으로 서버와 브라우저 간의 데이터를 교환할 수 있는 통신 기능을 의미한다. 빠르게 동작하는 동적인 웹 페이지를 만들기 위한 개발 기법의 하나이다.
즉, Ajax는 웹 페이지 전체를 다시 로딩하지 않고, 웹 페이지의 일부분만을 갱신하는 방법이다. Ajax는 jQuery 고유의 기능은 아니지만 jQuery는 간단한 문법으로 Ajax를 사용할 수 있게 해준다.



✅콜백 함수로 비동기 처리 방식의 문제점 해결하기

앞의 예제를 통해 자바스크립트 비동기 처리 방식에 의해 야기될 수 있는 문제들을 살펴봤다. 이러한 문제들은 어떻게 해결할 수 있을까? 바로 콜백함수를 이용하는 것이다. 함수로부터 결과값을 리턴 받기를 포기하고, 결과값을 이용해서 처리할 로직을 콜백 함수에 담아 인자로 던지면 된다.

앞서 살펴본 setTimeout( ) 코드를 콜백 함수로 개선해보자.

function findUserAndCallBack(id, cb) {
  setTimeout(function () {
    console.log("waited 0.1 sec.");
    const user = {
      id: id,
      name: "User" + id,
      email: id + "@test.com",
    };
    cb(user);
  }, 100);
}

findUserAndCallBack(1, function (user) {
  console.log("user:", user);
});


// waited 0.1 sec.
// user: {id: 1, name: "User1", email: "1@test.com"}

앞서 살펴본 ajax 통신 코드를 콜백 함수로 개선해보자.

function getData(callbackFunc) {
	$.get('https://domain.com/products/1', function(response) {
		callbackFunc(response); // 서버에서 받은 데이터 response를 callbackFunc()에 넘겨줌
	});
}

getData(function(tableData) {
	console.log(tableData); // $.get()의 response 값이 tableData에 전달됨
});

이렇게 콜백 함수를 사용하면 특정 로직이 끝났을 때 원하는 동작을 실행시킬 수 있다.


✅콜백 지옥 (Callback hell)

자바스크립트는 비동기 처리를 위한 하나의 패턴으로 콜백 함수를 사용한다. 하지만 전통적인 콜백 패턴은 콜백 지옥으로 인해 가독성이 나쁘고 비동기 처리 중 발생한 에러의 처리가 곤란하며, 여러 개의 비동기 처리를 한번에 처리하는데도 한계가 있다.

콜백 지옥은 비동기 처리 로직을 위해 콜백 함수를 연속해서 사용할 때 발생하는 문제이다.
웹 서비스를 개발하다 보면 서버에서 데이터를 받아와 화면에 표시하기까지 인코딩, 사용자 인증 등을 처리해야 하는 경우가 있다. 만약 이 모든 과정을 비동기로 처리해야 한다고 하면 콜백 안에 콜백을 계속 무는 형식으로 코딩을 하게 된다. 이러한 코드 구조는 가독성이 떨어지고 로직을 변경하기도 어렵다.
→ 콜백 지옥을 해결하기 위해 Promise or Async를 사용한다.

비동기의 콜백지옥이 문제라면 동기로 처리하면 안될까?
동기로 처리하게 되면 요청 후 응답이 오기 전까지 브라우저가 굳어버린다.
ex) API 조회에 10초가 걸린다면 10초간 브라우저가 먹통이 된다.



✅Promise

ES6에서는 비동기 처리를 위한 또 다른 패턴으로 Promise를 도입했다. 프로미스는 전통적인 콜백 패턴이 가진 단점을 보완하며 비동기 처리 시점을 명확하게 표현할 수 있다는 장점이 있다.

Promise 개념

Promise는 현재에는 당장 얻을 수는 없지만 가까운 미래에는 얻을 수 있는 어떤 데이터에 접근하기 위한 방법을 제공한다. 당장 원하는 데이터를 얻을 수 없다는 것은 데이터를 얻는데까지 지연 시간(delay, latency)이 발생하는 경우를 말한다. I/O나 Network를 통해서 데이터를 얻는 경우가 대표적인데, CPU에 의해서 실행되는 코드 입장에서는 엄청나게 긴 지연 시간으로 여겨지기 때문에 Non-blocking 코드를 지향하는 자바스크립트에서는 비동기 처리가 필수적이다.

콜백 함수를 통해 비동기 처리를 하던 기존 코드와 가장 큰 차이점은 함수를 호출하면 Promise 타입의 결과값이 리턴되고, 이 결과값을 가지고 다음에 수행할 작업을 진행한다는 것이다. 따라서 기존 스타일보다 비동기 처리 코드임에도 불구하고 마치 동기 처리 코드처럼 코드가 읽히기 때문에 좀 더 직관적이다.

Promise 생성

Promise는 비동기 작업의 완료 or 실패를 나타내는 객체로, new Promise( ) 생성자 함수를 통해 인스턴스화한다. new Promise( )는 비동기 작업을 수행할 콜백 함수를 인자로 전달받는데, 이 콜백 함수는 resolvereject 함수를 인자로 전달받는다.

resolvereject 함수는 정상 처리, 예외 발생 여부에 따라 적절히 호출해줘야 한다. 일반적으로 resolve의 인자로는 미래 시점에 얻게될 결과를 넘겨주고, reject의 인자로는 미래 시점에 발생할 예외를 넘겨준다.

const promise = new Promise((resolve, reject) => {
  if (/* 비동기 작업 수행 성공 */) {
    resolve('result');
  }
  else { /* 비동기 작업 수행 실패 */
    reject('failure reason');
  }
});

Promise는 비동기 처리에 대한 상태 정보를 갖는다.
new Promise( ) 생성자 함수로 프로미스가 생성된 직후부터 resolve, reject 함수가 호출되기 전까지는 pending 상태이다. 생성자 함수가 인자로 전달받은 콜백 함수는 내부에서 비동기 처리 작업을 수행한다. 이때 비동기 처리가 성공하면 콜백 함수의 인자로 전달받은 resolve 함수를 호출한다. 이때 프로미스는 fulfilled 상태가 된다. 비동기 처리가 실패하면 reject 함수를 호출한다. 이때 프로미스는 rejected 상태가 된다.

Promise method chaining

Promise는 then, catch 를 통해 동기 처리 코드에서 사용하던 try-catch 블록과 유사한 방법으로 비동기 처리 코드를 작성할 수 있도록 해준다. Promise로 구현된 비동기 함수를 호출하는 측에서는 Promise 객체의 후속 처리 메소드 then, catch 를 통해 결과 or 에러 메시지를 전달받을 수 있다.

then, catch는 또 다른 Promise 객체를 리턴한다. 그리고 이 Promise 객체는 인자로 넘긴 콜백 함수의 리턴값을 다시 then, catch 를 통해 접근할 수 있도록 해준다.
Promise method chaining

then은 바로 이전 then의 출력값을 입력값으로 사용하여 새로운 출력값을 만들고, 바로 다음 then의 입력값으로 넘겨준다.

then

then은 2개의 콜백 함수를 인자로 전달받는다. 첫 번째 콜백 함수는 성공 시 (fulfilled) 호출되고, 두 번째 함수는 실패 시 (rejected) 호출된다. then은 새로운 promise를 반환한다. 처음에 만들었던 promise와는 다른 새로운 promise이다.

const promise = doSomething();
const promise2 = promise.then(successCallback, failureCallback);

const promise2 = doSomething().then(successCallback, failureCallback)

🐣 예시

doSomething(function(result) {
  doSomethingElse(result, function(newResult) {
    doThirdThing(newResult, function(finalResult) {
      console.log('Got the final result: ' + finalResult);
    }, failureCallback);
  }, failureCallback);
}, failureCallback);

위의 콜백 지옥 코드를 다음과 같이 바꾼다.

doSomething().then(function(result) {
  return doSomethingElse(result);
})
.then(function(newResult) {
  return doThirdThing(newResult);
})
.then(function(finalResult) {
  console.log('Got the final result: ' + finalResult);
})
.catch(failureCallback);

이를 화살표 함수로 표현하면 다음과 같다.

doSomething()
.then(result => doSomethingElse(result))
.then(newResult => doThirdThing(newResult))
.then(finalResult => {
  console.log(`Got the final result: ${finalResult}`);
})
.catch(failureCallback);

catch

예외 (비동기 처리에서 발생한 에러, then 메소드에서 발생한 에러)가 발생하면 호출된다.
catch( ) 메서드는 예외 처리 로직을 담은 콜백 함수를 인자로 받는다.

new Promise((resolve, reject) => {
    console.log('Initial');

    resolve();
})
.then(() => {
    throw new Error('Something failed');

    console.log('Do this');
})
.catch(() => {
    console.log('Do that');
})
.then(() => {
    console.log('Do this, whatever happened before');
});

/*
Initial
Do that
Do this, whatever happened before */

🐣 Promise를 이용해 메소드 체이닝하는 코딩 스타일은 async/await 키워드를 사용하는 방식으로 대체되고 있는 추세이다.


Promise 메소드

Promise.all

Promise.all 메소드는 Promise가 담겨 있는 배열 등의 이터러블을 인자로 전달 받는다. 전달받은 모든 Promise를 병렬로 처리하고 그 처리 결과를 resolve 하는 새로운 Promise를 반환한다. Promise 처리가 모두 완료되면 then이 호출된다. 여러 개의 Promise를 동시에 처리할 때 유용하다.

consr delay = (time) => new Promise(resolve => setTimeout(resolve,time))
const promise1 = delay(1000)
const promise2 = delay(2000)
const promise2 = delay(3000)

Promise.all([promise1,promise2,promise3]).then(()=>{
  // promise가 모두 처리된 후 호출됨 
})
Promise.all([
  new Promise(resolve => setTimeout(() => resolve(1), 3000)), // 1
  new Promise(resolve => setTimeout(() => resolve(2), 2000)), // 2
  new Promise(resolve => setTimeout(() => resolve(3), 1000))  // 3
]).then(console.log) // [ 1, 2, 3 ]

Promise.race

Promise.race 메소드는 Promise.all 메소드와 동일하게 Promise가 담겨 있는 배열 등의 이터러블을 인자로 전달받는다. 하지만 Promise.race 메소드는 Promise.all 메소드처럼 모든 Promise를 병렬 처리하는 것이 아니라, 여러 개의 Promise 중 하나라도 처리가 완료되면 메소드가 종료된다. 즉, 가장 먼저 처리된 Promise가 resolve한 처리 결과를 resolve하는 새로운 Promise를 반환한다.

Promise.race([
  new Promise(resolve => setTimeout(() => resolve(1), 3000)), // 1
  new Promise(resolve => setTimeout(() => resolve(2), 2000)), // 2
  new Promise(resolve => setTimeout(() => resolve(3), 1000))  // 3
]).then(console.log) // 3
  .catch(console.log);

에러가 발생한 경우는 Promise.all 메소드와 동일하게 처리된다. 즉, Promise.race 메소드에 전달된 Promise 처리가 하나라도 실패하면 가장 먼저 실패한 Promise가 reject한 에러를 reject하는 새로운 프로미스를 즉시 반환한다.

Promise.race([
  new Promise((resolve, reject) 
              => setTimeout(() => reject(new Error('Error 1!')), 3000)),
  new Promise((resolve, reject) 
              => setTimeout(() => reject(new Error('Error 2!')), 2000)),
  new Promise((resolve, reject) 
              => setTimeout(() => reject(new Error('Error 3!')), 1000))
]).then(console.log)
  .catch(console.log); // Error: Error 3!

Promise.any

Promise.any 메소드는 Promise.race 메소드와 비슷하다. Promise.race는 여러 Promise 중 하나라도 resolve or reject 되면 종료되었다. Promise.any는 여러 Promise 중 하나라도 resolve되면 종료된다.

Promise.allSettled

여러 Promise들이 성공했거나 실패했거나 상관없이 모두 이행된 경우를 처리할 수 있다.

Promise.resolve/Promise.reject

Promise.resolvePromise.reject 메소드는 존재하는 값을 Promise로 래핑하기 위해 사용한다. Promise.resolve는 인자로 전달된 값을 resolve하는 Promise를 생성한다. Promise.reject 메소드는 인자로 전달된 값을 reject하는 Promise를 생성한다.

위 예제는 아래 예제와 동일하게 동작한다.

const resolvedPromise = Promise.resolve([1, 2, 3]);
const rejectedPromise = Promise.reject(new Error('Error!'));
const resolvedPromise = new Promise(resolve => resolve([1, 2, 3]));
const rejectedPromise = new Promise((resolve, reject) 
                                   => reject(new Error('Error!')));

Promise 사용법 (feat.fetch 함수)

실제 코딩을 할 때는 Promise를 직접 생성해서 리턴해주는 코드보다는 어떤 라이브러리의 함수를 호출해서 리턴받은 Promise 객체를 사용하는 경우가 더 많다.

REST API를 호출할 때 사용되는 브라우저 내장 함수인 fetch( )가 대표적인데, fetch( ) 함수는 API의 URL을 인자로 받고, 미래 시점에 얻게될 API 호출 결과를 Promise 객체로 리턴한다. network latency 때문에 바로 결과값을 얻을 수 없는 상황이므로 Promise 사용 목적에 정확히 부합한다.

예를 들어 fetch( ) 함수를 이용해서 어떤 서비스의 API를 호출 후, 정상 응답 결과를 출력해보자.

fetch("https://jsonplaceholder.typicode.com/posts/1")
  .then((response) => console.log("response:", response))
  .catch((error) => console.log("error:", error));

인터넷 상에서 유효한 URL을 fetch( ) 함수의 인자로 넘겼기 때문에 예외가 발생하지 않고, then( )에 인자로 넘긴 콜백 함수가 호출되어 상태 코드 200의 응답이 출력되었다.

이번에는 fetch( ) 함수의 인자로 URL을 넘기지 말아보자. catch( ) 메서드의 인자로 넘긴 콜백 함수가 호출되어 에러 정보가 출력되었음을 알 수 있다.

fetch()
  .then((response) => console.log("response:", response))
  .catch((error) => console.log("error:", error));


Promise 문제점

REST API를 호출하여 게시물 작성자의 이름을 리턴하는 함수를 작성하고 그 함수를 호출해보자.

function fetchAuthorName(postId) {
  return fetch(`https://jsonplaceholder.typicode.com/posts/${postId}`)
    .then((response) => response.json())
    .then((post) => post.userId)
    .then((userId) => {
      return fetch(`https://jsonplaceholder.typicode.com/users/${userId}`)
        .then((response) => response.json())
        .then((user) => user.name);    // 8번째 줄 
    });
}

fetchAuthorName(1).then((name) => console.log("name:", name));

여기에는 여러가지 문제점이 있다.

  • 디버깅
    위 코드의 8번째 줄을 .then(user => user1.name); 에러가 발생하도록 의도적으로 수정 후에 코드를 실행을 해보면 다음과 같은 에러 메시지가 나온다.

    ReferenceError: user1 is not defined
    at fetch.then.then.then.then.then (<anonymous>:7:29)

    then을 연쇄적으로 호출하고 있어서 도대체 몇 번째 then에서 문제가 발생한 건지 Stack Trace을 보더라도 혼란스러울 수 있다.
    또한 then 호출부에 break point를 걸고 디버거를 돌리면, 위 코드와 같이 화살표 함수로 한 줄짜리 콜백 함수를 넘긴 경우에는 코드 실행이 break point에서 멈추지 않기 때문에 디버깅이 상당히 불편하다.

  • 예외 처리
    Promise를 사용하면 try-catch 대신에 catch를 사용하여 예외를 처리해야 한다. 이 부분이 비동기 코드만 있을 때는 그렇게 거슬리지 않는데, 동기 코드와 비동기 코드가 섞여 있을 경우 예외 처리가 난해해지거나 예외 처리를 누락하는 경우가 생기기 쉽다.

  • 들여쓰기
    실제 프로젝트에서는 샘플 코드와 같이 간단한 구조가 아닌 복잡한 구조의 비동기 처리 코드를 작성하게 된다. 따라서 then의 인자로 넘기는 콜백 함수 내에서 조건문이나 반복문을 사용하거나 여러 개의 Promise를 병렬로 또는 중첩해서 호출해야하는 경우들이 발생힌다. 이럴 경우 다단계 들여쓰기를 해야 할 확률이 높아지며 코드 가독성은 점점 떨어진다.


    ❗ Promise의 이러한 불편한 점들을 해결하기 위해 ES7에서 async/await 키워드가 추가되었다. async/await 키워드를 사용하면 비동기 코드를 마치 동기 코드처럼 보이게 작성할 수 있다.


✅Async & Await

async, await는 자바스크립트의 비동기 처리 패턴 중 가장 최근에 나온 문법이다. 기존의 비동기 처리 방식인 콜백 함수와 프로미스의 단점을 보완하고 개발자가 읽기 좋은 코드를 작성할 수 있게 도와준다.

async & await 사용법

async는 function 앞에 위치한다. function 앞에 async를 붙이면 해당 함수는 항상 Promise를 반환한다. awaitasync 함수 안에서만 동작하는데, Promise를 리턴하는 모든 비동기 함수 호출부 앞에 await를 추가하면 비동기 함수가 리턴하는 Promise로부터 결과값을 추출해준다.

즉, await를 사용하면 일반 비동기 처리처럼 실행이 다음 라인으로 바로 넘어가는 것이 아니라 결과값을 얻을 때까지 기다려준다. 따라서 일반적인 동기 코드 처리와 동일한 흐름으로 코드를 작성할 수 있으며, 따라서 코드 읽기도 한결 수월해진다. 또한 Promise가 처리되길 기다리는 동안엔 엔진이 다른 일을 할 수 있기 때문에 CPU 리소스가 낭비되지 않는다.


🐣 예시 1

async function f() {
  let promise = new Promise((resolve, reject) => {
    setTimeout(() => resolve("완료!"), 1000)
  });
  let result = await promise; // Promise가 이행될 때까지 기다림 (*)
  alert(result); // "완료!"
}

f();

함수를 호출하고, 함수 본문이 실행되는 도중에 (*)로 표시한 줄에서 실행이 잠시 중단되었다가 Promise가 처리되면 실행이 재개된다. 이때 Promise 객체의 결과값이 변수 result에 할당된다. 따라서 위 예시를 실행하면 1초 뒤에 '완료!'가 출력된다.


🐣 예시 2

'Promise 문제점'에서 살펴본 REST API를 호출하여 게시물 작성자의 이름을 리턴하는 함수를 async/await 키워드를 이용하여 재작성 해보자.

async function fetchAuthorName(postId) {
  const postResponse = await fetch(
    `https://jsonplaceholder.typicode.com/posts/${postId}`
  );
  const post = await postResponse.json();
  const userId = post.userId;
  const userResponse = await fetch(
    `https://jsonplaceholder.typicode.com/users/${userId}`
  );
  const user = await userResponse.json();
  return user.name;
}

fetchAuthorName(1).then((name) => console.log("name:", name));

❗한가지 주의할 점은 async가 붙어있는 함수를 호출하면 명시적으로 Promise 객체를 생성하여 리턴하지 않아도 Promise 객체가 리턴된다. 따라서 호출부를 보면 then 메소드를 통해 결과값을 출력하고 있다.

async & await 예외 처리

async/await에서 예외 처리하는 방법은 바로 try-catch이다. 따라서 동기, 비동기 구분없이 try-catch로 일관되게 예외 처리할 수 있다.

async function f() {
  try {
    let response = await fetch('http://유효하지-않은-주소');
    let user = await response.json();
  } catch(err) {
    // fetch와 response.json에서 발행한 에러 모두를 여기서 잡는다.
    alert(err);
  }
}

f();

요약

function 앞에 async 키워드를 추가하면 두 가지 효과가 있다.

  1. 함수는 언제나 Promise를 반환한다.
  2. 함수 안에서만 await를 사용할 수 있다.

Promise 앞에 await 키워드를 붙이면 자바스크립트는 Promise가 처리될 때까지 대기하다가 처리가 완료되면 조건에 따라 아래와 같은 동작이 이어진다.

  • 에러 미발생 : Promise 객체의 결과값 반환
  • 에러 발생 : 예외가 생성됨 (에러가 발생한 장소에서 throw error를 호출한 것과 동일)
    async function f() {
    await Promise.reject(new Error("에러 발생!"));
    }
    // 위 코드와 아래 코드는 동일 
    async function f() {
    throw new Error("에러 발생!");
    }

async/await를 함께 사용하면 읽고 쓰기 쉬운 비동기 코드를 작성할 수 있고 promise.then/catch가 거의 필요없다. 하지만 가끔 가장 바깥 스코프에서 비동기 처리가 필요할 때 같이 promise.then/catch를 써야만 하는 경우가 생긴다. (문법 제약 때문에 async 함수 바깥의 최상위 레벨 코드에선 await를 사용할 수 없다.)
따라서 async/await가 Promise를 기반으로 한다는 사실을 알아야 한다.

❗브라우저에 따라 top level await 가 등장하여 top level에서도 사용 가능하다.

profile
오늘도 신나개 🐶

0개의 댓글