JavaScript 실행 컨텍스트, 클로저!

Promise

비동기 특성상 이벤트 처리 콜백 메서드의 순서를 지정할 수 없는데 Promise 객체를 사용하면
문제를 해결할 수 있을 뿐 아니라 콜백 지옥을 해결 가능하다.

https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Promise

then, catch, finally 3가지 콜백함수 등록이 가능

finally 는 ES2018 에 추가됨

var p = new Promise(function (resolve, reject) {
  /* 비동기적으로 수행할 작업 */
  if(...) {
      resolve(num); // 작업성공시 호출할 콜백
  } else {
      reject(num); // 작업 실패시 호출할 콜백
  }
});

// 위의 작업이 끝나 후 호출할 성공/실패 콜백 메서드 정의
p.then(function (num) {
    console.log("resolve invoked, num:", num); // 작업 성공 콜백
}).catch(function (num) {
    console.log("reject invoked, num:", num); // 작업 실패 콜백
}).finalnny(() => {
    console.log("Done!")
})

p 내부의 비동기 코드 수행 완료 후 resolve-then, reject-catch 세트로 정의된 콜백메서드가 호출된다.

아래처럼 then 메서드에 resolve, reject 메서드를 모두 정의해도 된다.

p.then(
  (result) => console.log("resolve invoked, num:", result),
  (error) => console.log("reject invoked, num:", error)
);

Promise Chaining - all

위의 then, catch 메서드는 또다른 Promise 객체를 반환하기 때문에 then, catch 를 여러개 묶을 수 있다.

const promiseFirst = new Promise(resolve => resolve(1)) // 그냥 실행 성공 콜백에 1 전달
    .then(result => `${result + 10}`) // 11 반환
    .then(result => {console.log(result); return result + 1;}) // 11출력 111 반환

const promiseSecond = new Promise(resolve => resolve(1))
    .then(result => `${result + 20}`) // 21 반환
    .then(result => {console.log(result); return result + 1;}) // 21 출력 211 반환

// 2개의 비동기 메서드 결합
Promise.all([promiseFirst, promiseSecond]).then(result => console.log(result))

문자열로 인식되어 1이 붙어 111, 211 로 출력된다.

11
21
[ '111', '211' ]

then 에 정의된 람다식을 실행 후 resolve 를 호출, 아래의 then 의 정의된 람다식을 호출, 이를 반복하여 chaining 을 구성한다.

Promise.all 전역 메서드를 사용해 여러개의 Promise 객체를 병합하여 새로운 Promise 객체를 생성하는 것도 가능하다.

async, await

async, await 문법을 사용하면 Promise 를 좀 더 편하게 사용할 수 있다.

함수 앞에 비동기 함수임을 뜻하는 async 키워드 사용

값을 반환할 필요는 없지만 반환값이 있을경우 항상 Promise 객체로 감싸서 반환한다.

async function f() {
  return 1;
}
// 아래 함수와 동일함
// function f() {
//   return Promise.resolve(1);
// } 
f().then(alert); // 1

await 키워드는 async 메서드 내부에서만 사용가능하며 Promise 를 동기적으로 동작하도록 구성한다.

async function foo() {
    await 1
}
// 아래 함수와 동일함
// function foo() {
//     return Promise.resolve(1).then(() => undefined)
// }
f().then(alert); // 1

await 는 주로 네트워크 요청이나 파일읽기 같은 시간이 오래걸리는 함수호출문 앞에 사용한다.

const fs = require('fs');

const _p = (val) => {
    return new Promise((resolve, reject) => {
        fs.readFile('./test.txt', (err, data) => {
            if (err) {
                reject(err)
            }
            resolve(`${val}: ${data.toString()}`)
        })
    })
}

async function p() {
    try {
        var data1 = await _p(1);
        var data2 = await _p(2);
        var data3 = await _p(3);
        console.log(data1, data2, data3);
    } catch (err) {
        console.log('error! ' + err);
    }
}

p();

try, catch 를 사용해 비동기 메서드 안에서 reject 가 호출되거나 throw 로 예외를 발생시킨 것을 쉽게 처리할 수 있다.

비동기 처리 메서드는 Promise 객체를 반환해야 await가 의도한 대로 동작한다.

그렇다고 async, await 키워드로 비동기 처리를 하려고 꼭 Promise 객체를 정의할 필요는 없다.
아래처럼 async 와 람다식을 사용해 비동기 메서드 정의가 가능하다.

const originPromise = new Promise((resolve, reject) => {
    console.log("origin called")
    resolve("origin promise");
});

originPromise.then(res => {
    console.log(res);
});

const newMetaPromise = async () => {
    console.log("new meta called")
    return "new meta promise";
}
newMetaPromise().then(res => {
    console.log(res);
});
// origin called
// new meta called
// origin promise
// new meta promise

비동기 처리를 위해 callback을 마지막 매개변수로 넘겨주거나 Promise 객체를 정의하는 것 보다
아래처럼 비동기 메서드를 async, await 키워드로 한번 더 감싸면 코드는 조금 늘어나도 비동기적 사고는 적게할 수 있다.

const newMetaPromise = async () => {
    console.log("new meta called")
    return "new meta promise";
}

async function test() {
    let result = await newMetaPromise();
    console.log(result);   
}

test();

비동기 반복

반복문 내부에 비동기 구문이 있을 경우
반복문의 완료여부를 기다리고 싶을 때 비동기 반복문을 사용

import axios from "axios";

const params = [1, 2, 3, 4];

const resArray = [];
params.forEach(async param => {
  const res = await axios.get(`https://jsonplaceholder.typicode.com/todos/${param}`);
  resArray.push(res.data);
});

console.log(resArray); // []

위처럼 비동기반복문을 사용할 경우 빈 배열이 출력된다.

import axios from "axios";

const params = [1, 2, 3, 4];

const resArray = [];
for await (const param of params) {
  const res = await axios.get(`https://jsonplaceholder.typicode.com/todos/${param}`);
  resArray.push(res.data);
}

console.log(resArray); // [x, x, x, x]

for await ...of 구문을 사용하면 반복문 내부의 모든 비동기함수가 완료될 때 까지 다음 구문을 대기한다.