[JavaScript]Promise(비동기처리)

OnStar·2021년 9월 2일
0

JavaScript

목록 보기
3/5
post-thumbnail

자바스크립트는 싱글 쓰레드로 동작하는 언어이지만 비동기적 처리가 가능하다.

비동기처리란?

"멀티테스킹"
A 작업은 1시간이 걸리는 작업이고 B 작업은 1초가 걸리는 작업이라고 할 때,
동기적인 처리 방법에서는 A 작업이 끝나야지만 B 작업이 시행된다.
1초가 걸릴 작업이 1시간 1초가 걸리는 셈이다.
[웹사이트에서 이런 동기적 처리만 가능하다면 큰 문제가 발생할 것이다.]
따라서 A의 완료 여부와 상관없이 B 작업을 먼저 실행해주는 것을 비동기 처리라고 한다.
자바스크립트는 싱글 쓰레드 엔진을 갖고있지만 이러한 비동기적 처리가 가능하다.

Thread(쓰레드)란?

프로세스는 메모리 상에서 실행중인 작업을 뜻하며, 이러한 프로세스 내의 실행 단위를 쓰레드라고 한다.

프로세스: 각각의 은행 지점
스레드: 은행 지점 하나에 속한 고객 창구 여러 개

쓰레드가 하나인 경우 쓰레드에서 호출되는 함수들은 콜스택에 쌓이고 이 함수들은 LIFO 방식으로 한번에 하나의 함수만 동기적으로 실행이 가능하게된다. 즉, 원론적으로는 싱글 쓰레드 환경에서는 비동기적인 함수 실행을 할 수 없다.

그렇다면 싱글 쓰레드이지만 비동기 작업이 가능한 JS의 경우는?

자바스크립트의 런타임(Run-Time)


자바스크립트의 V8 엔진은 런타임시 WebAPI(dom, ajax, setTimeout...), Callback Queue, Event Loop 등과 함께 동작한다.

  • Memory Heap : 메모리 할당을 담당하는 곳으로 선언한 변수와 함수들이 들어간다.

  • Call Stack: 코드가 호출되면 LIFO 방식으로 실행되는 곳이다.
    (스택이 할당된 공간보다 많은 공간을 차지하게 되면 스택오버플로우 에러가 발생한다.)

  • Web api :브라우저에서 자체 지원하는 api로 Dom 이벤트, Ajax (XmlHttpRequest), setTimeout 등의 비동기 작업들을 수행할 수 있도록 api를 지원한다.

  • Event Loop : 이벤트 발생 시 호출되는 콜백 함수들을 관리하여 태스크 큐에 전달하고, 콜스택이 비어있을때 태스크 큐에 담겨있는 콜백 함수들을 콜스택에 넘겨준다.
    (콜스택의 함수들이 먼저 실행된다)

  • Callback Queue : Web API에서 비동기 작업들이 실행된 후 호출되는 콜백함수들이 기다리는 공간이다. 이벤트 루프가 정해준 순서대로 줄을 서있으며, FIFO방식을 따른다.

자바스크립트 런타임시 같이 작동하는 WebAPI, Callback Queue, Event Loop 를 통해 비동기 작업이 가능하게된다.

JS 속 비동기 작업의 수행 과정

  1. Call Stack 에서 비동기 함수가 실행되면, 자바스크립트 엔진은 Web API에게 비동기 작업을 위임한다.
  2. Web API는 해당 비동기 작업을 수행한 후, 콜백함수를 Event Loop를 통해 Callback Queue에게 넘겨준다.
  3. Event LoopCall Stack이 비어있을 때, Callback Queue의 콜백함수를 Call Stack으로 넘겨준다.
  4. 콜백함수가 실행되고 Call Stack에서 제거된다.

Call Back?

콜백은 자바스크립트가 비동기 처리를 하기 위한 패턴 중 하나이다.
A 함수에서 자동으로 B 함수가 실행되도록 B 함수를 콜백하는 것이다.
콜백에는 제한이 없기 때문에 B함수에서 C 함수를 콜백할 수 있고, C 함수에서 D 함수를 콜백 하는 등 꼬리에 꼬리를 무는 콜백이 가능하다.

function async1('a', function (err, async2){
  if(err){
    errHandler(err);
  }else{
    ...
    async2('b', function (err, async3){
      ...
    }) {
      ...
    }
  }
});

꼬리에 꼬리를 무는 비동기 처리 콜백이 늘어나면 코드는 깊어지고, 관리는 어려워지는 콜백 헬이 발생한다. 또한 비동기 처리 시에는 실행 완료를 기다리지 않고 바로 다음 작업을 실행하기 때문에 작업 실행의 순서가 엉키거나 메모리 사용상에도 문제가 발생할 수 있다.

이러한 콜백 헬을 해결하기위해 ES6 부터 Promise 라는 개념을 도입하였다.

Promise?

ES6부터 도입된 비동기 처리 패턴으로 비동기 연산이 종료된 이후 결과를 알기 위해 사용하는 객체이다.
Promise를 사용하면 비동기 메서드를 마치 동기 메서드처럼 값을 반환할 수 있다.
즉, 비동기 처리 시점을 더 명확하게 표현할 수 있다.
["작업 B를 다른 작업들과는 비동기적으로 수행하지만 단서 조항으로 작업 A가 끝나야지만 실행해"]

resolve,reject를 인자로 갖는 실행 함수를 인자에 넣는 Promise 생성자

new Promise((resolve, reject) => { ... });
resolve: 작업이 성공한 경우 호출할 콜백
reject: 작업이 실패한 경우 호출할 콜백

// new Promise((resolve, reject) => { ... }); 
// resolve: 작업이 성공한 경우 호출할 콜백
// reject: 작업이 실패한 경우 호출할 콜백
const promise = new Promise((resolve, reject) => {
  if(...){
     ...
     resolve("성공!");
  } else{
     ...
     reject("실패!");
  }
});

프라미스의 상태값

  • pending: 비동기 처리 수행 전(resolve, reject가 아직 호출되지 않음)
  • fulfilled: 수행 성공(resolve가 호출된 상태)
  • rejected: 수행 실패(reject가 호출된 상태)
  • settled: 성공 or 실패(resolve나 reject가 호출된 상태)

프라미스 후속 처리 메서드

프라미스로 구현된 비동기 함수는 프라미스 객체를 반환한다.

프라미스로 구현된 비동기 함수를 호출하는 측에서는 이 프라미스 객체의 후속 처리 메서드를 통해 비동기 처리 결과(성공 결과나 에러메시지)를 받아서 처리해야 한다.

.then(성공 시, 실패 시)

then의 첫 인자는 성공 시 실행, 두번째 인자는 실패 시 실행된다. (첫 번째 인자만 넘겨도 실행됨)

let promise = new Promise((resolve, reject) => {
  setTimeout(() => resolve("완료!"), 1000);
});
// resolve
promise.then(result => {
  console.log(result); // 완료!가 콘솔에 찍힌다.
}, error => {
  console.log(error); // 실행되지 않는다.
});
let promise = new Promise((resolve, reject) => {
  setTimeout(() => reject(new Error("오류!")), 1000);
});
// reject
promise.then(result => {
  console.log(result); // 실행되지 않는다.
}, error => {
  console.log(error); // Error: 오류!가 찍힌다.
});

.catch(실패 시)

let promise = new Promise((resolve, reject) => {
  setTimeout(() => reject(new Error("오류!"), 1000);
             });
promise.catch((error) => {console.log(error};);

Promise Chainin

Promise 는 후속 처리 메서드(then)를 여러 개의 Promise로 연결할 수 있다.

new Promise((resolve, reject) => {
  setTimeout(() => resolve("promise 1"), 1000);
}).then((result) => { // 후속 처리 메서드 하나를 쓰고,
  console.log(result); // promise 1
  return "promise 2";
}).then((result) => { // 이렇게 연달아 then을 써서 이어준다.
  console.log(result);
  return "promise 3";
}).then(...);

콜백을 해결했지만 Promise의 사용은 직관적이지 않고 간결하지 않기 때문에 비동기처리의 간편화를 위해 ES7 부터 async/await 기능을 추가하였다.

async / await ?

async

함수 앞에 async를 붙여서 사용한다.
항상 Promise를 반환하여 Promise가 아닌 값도 Promise를 감싸서 반환해준다.

// async는 function 앞
async function myFunc() {
  return "프라미스 반환";
}
myFunc().then(result => {console.log(result)}); 

await

async 함수 안에서만 동작한다. 즉, async 없이는 사용할 수 없다.
await는 프라미스가 처리될 때까지 기다렸다가 그 이후에 결과를 반환한다.

async function myFunc(){
  let promise = new Promise((resolve, reject) => {
    setTimeout(() => resolve("완료!"), 1000);
  });
  console.log(promise);
  let result = await promise; // promise가 끝나도록 기다림
  console.log(promise);
  console.log(result); // then(후처리 함수)를 쓰지 않았는데도, 1초 후에 완료!가 콘솔에 찍힌다.
}

await를 만나면, 실행이 잠시 중단되었다가 프라미스 처리 후에 실행을 재개한다.즉, await를 쓰면 함수 실행을 기다리게 하는 것이다.

0개의 댓글