Javascript 비동기 처리 방법에는 Promise, async/await, callback 등의 방법이 있습니다.
JavaScript의 비동기 처리 방식은 비차단 코드 실행을 허용하여 서로가 완료될 때까지 기다리지 않고 여러 작업을 동시에 수행할 수 있도록 합니다.
이는 callbacks, promises, async/await, and event-driven 및 이벤트 기반 프로그래밍을 사용하여 달성됩니다.

Promise

Promise는 비동기 처리에 사용되는 Javascript 객체 입니다.

Promise를 관리하는 Status 상태값

Promise는 3가지 상태를 가지고 있고, 각각의 개별 상태 값은 직접 접근 할 수 없습니다.

Status value desc
초기 pending(대기) 초기 상태, 비동기 처리를 진행중인 상태
resolve fulfilled(이행) resolve 호출시, 처리가 완료된 의미
reject rejected(실패) reject 호출시, 실패 또는 오류가 발생한 상태

기본 사용 형태

let promise = new Promise(function(resolve, reject) {
    // 실행코드
    resolve(value) // 성공 완료시 value와 함께 호출
    reject(error) //실행 실패시 error와 함께 호출
});

Syntax

let promise = new Promise(function(resolve, reject) {
    //Promise는 성공 또는 실패에 대한 응답을 진행해야함.
    resolve("Success");
    reject(new Error("Fail"));
});

then

then 2개의 콜백 인수를 가지고 있고, Promise의 최종 처리 결과를 수신 합니다.

promise.then(
    function(result) { 
        // resolve 호출시
    },
    function(error) { 
        // reject 호출시
    }
);

// 축약
promise.then(
  result => console.log(result),
  error =>  console.log(error)
);

하기 코드는 3초뒤 성공을 Return하는 코드 입니다.

let promise = new Promise(function(resolve, reject) {
  //setTimeout(() => resolve("Success"), 3000);
  setTimeout(() => reject(new Error("Fail")) , 3000);
});

// reject 함수는 .then의 두 번째 함수를 실행합니다.
promise.then(
  result => console.log(`SUCCESS : ${result}`),
  error =>  console.log(`ERROR : ${error}`)
);

catch

then(null, error) 과 같이 에러를 처리하기 위한 구문으로, cathc(error) 로 축약된 기능.

let promise = new Promise(function(resolve, reject) {
  setTimeout(() => reject(new Error("Fail")) , 3000);
});

promise.catch(
  error =>  console.log(`catch : ${error}`)     //catch : Error: Fail 출력
);

finally

promise에서 실행의 성공, 실패와 무관하게 마지막에 실행됨을 보장하는 함수 입니다.
데이터 조회시 로딩 바를 보이고, 숨기는 동작에 자주 활용 합니다.

new Promise((resolve, reject) => {
  setTimeout(() => resolve("success"), 2000)
})
.then(result => console.log(`then value : ${result}`) )
.finally(() => console.log(`finally`) );

promise.then(f,f) vs promise.then(f).catch(f)

promise.then(successFn, failFn) vs promise.then(successFn).catch(failFn)
promise.then(성공,실패)promise.then(성공).catch(실패)에 동작성을 하기 코드로 확인해보자.

// Case 1
let promise = new Promise(function(resolve, reject) {
  setTimeout(() => resolve("Success"), 3000);
});

promise.then(
  result => console.log(`SUCCESS : ${result}`), 
  error =>  console.log(`ERROR : ${error}`)
);

// 출력값
// SUCCESS : Success


// Case 2
let promise = new Promise(function(resolve, reject) {
  setTimeout(() => resolve("Success"), 3000);
});

promise.then(
    result => console.log(`SUCCESS : ${result}`)
).catch(
    error =>  console.log(`ERROR : ${error}`)
);

// 출력값
// SUCCESS : Success

Case 1, 2 모두 동일한 SUCCESS : Success 를 출력한다. Case 3, 4 의 변형된 소스를 파악해보면, 성공 응답과정 내에 내부적 이슈가 발생했을때를 가정한 소스 입니다.

// Case 3
let promise = new Promise(function(resolve, reject) {
  setTimeout(() => resolve("Success"), 3000);
});

promise.then(
  result =>{
    console.log(`SUCCESS : ${result}`), 
    reject(new Error("Fail"));   //처리 중간 실패를 전달    
  },
  error =>  console.log(`ERROR : ${error}`)
);

// 출력값
// SUCCESS : Success
// Uncaught (in promise) ReferenceError: reject is not defined <- 소스 핸들링 범위 밖으로 에러가 발생함
   

// Case 4
let promise = new Promise(function(resolve, reject) {
  setTimeout(() => resolve("Success"), 3000);
});

promise.then(
    result =>{
    console.log(`SUCCESS : ${result}`), 
    reject(new Error("Fail"));   //처리 중간 실패를 전달    
   } 
).catch(
    error =>  console.log(`ERROR : ${error}`)
);

// 출력값
// SUCCESS : Success
// ERROR : ReferenceError: reject is not defined  <- 함수를 통해서 출력됨

Case 3, 4 의 응답은 다른 결과를 나타 냅니다.
promise.then(successFn, failFn) 형태의 응답은 중간 처리과정에서의 에러 처리가 Uncaught 처리될 수 있습니다.
완벽하게 Case를 모두 처리가 가능하면 문제가 되지 않으나, 예상하지 못하는 에러가 발생시 최악의 경우 에러발생시 소스 실행이 멈출수 있습니다.
반면 promise.then(successFn).catch(failFn) 방식의 실행은 에러 처리를 catch(failFn)에서 전담하여, 일괄 관리 할 수 있는 차이점 있습니다.

Promise API

Promise.resolve()

성공한 경우 호출하는 함수… 위의 예제 참조.

Promise.reject()

실패한 경우 호출하는 함수… 위의 예제 참조.

Promise.all()

다수의 Promise를 일괄 처리를 모두 완료하였을때 then의 핸들러 함수를 실행 할 수 있습니다.
syntax

let promise = Promise.all([...promise array...]);

Promise.all([
  new Promise((resolve, reject) => ... ),
  new Promise((resolve, reject) => ... ),
  new Promise((resolve, reject) => ... )
]).catch(error => ... ); 

사용 예제

let p1 = new Promise((resolve, reject) => {
  setTimeout(() => resolve("p1 성공"), 1000);
});

let p2 = new Promise((resolve, reject) => {
  setTimeout(() => resolve("p2 성공"), 2000);
});

let p3 = new Promise((resolve, reject) => {
  setTimeout(() => resolve("p3 성공"), 3000);
  //setTimeout(() => reject(new Error("p3 실패")) , 3000);
});

Promise
  .all([p1, p2, p3])
  .then((param)=> console.log(`then(${param.length}) : ${param}` ) )
  .catch(error =>  console.log(`ERROR : ${error}`)); 

// 모두 성공 반환시
// param 은 p1,p2,p3의 resolve 인수를 모두 배열로 반환 ['p1 성공', 'p2 성공', 'p3 성공']

// 한건 이상의 Fail 반환시 catch 호출
// catch 로직으로 진입함 ERROR : Error: p3 실패 

Promise.allSettled()

Promise.all과 달리 개별 건의 실행 결과를 받을 수 있습니다.
개별 Promise의 값, 상태 를 받아서 처리해야 될 경우 사용할 수 있습니다.

모두 성공한 경우 사용 예제

let p1 = new Promise((resolve, reject) => {
  setTimeout(() => resolve("p1 성공"), 1000);
});

let p2 = new Promise((resolve, reject) => {
  setTimeout(() => resolve("p2 성공"), 2000);
});

let p3 = new Promise((resolve, reject) => {
  setTimeout(() => resolve("p3 성공"), 3000);
  //setTimeout(() => reject(new Error("p3 실패")) , 3000);
});

Promise
  .allSettled([p1, p2, p3])
  .then((param)=> {console.log(`then(${param.length}) : ${param}` ); console.log(param);} )
  .catch(error =>  console.log(`ERROR : ${error}`)); 

// 응답값
then(3) : [object Object],[object Object],[object Object]
(3) [{}, {}, {}]
0 : {status: 'fulfilled', value: 'p1 성공'}
1 : {status: 'fulfilled', value: 'p2 성공'}
2 : {status: 'fulfilled', value: 'p3 성공'}


일부 실패가 존재하는 경우 사용 예제, 일부 실패 reject호출에도 catch로 진입하지 않고 then의 개별 Object 결과를 반환합니다.

let p1 = new Promise((resolve, reject) => {
  setTimeout(() => resolve("p1 성공"), 1000);
});

let p2 = new Promise((resolve, reject) => {
  setTimeout(() => resolve("p2 성공"), 2000);
});

let p3 = new Promise((resolve, reject) => {
  //setTimeout(() => resolve("p3 성공"), 3000);
  setTimeout(() => reject(new Error("p3 실패")) , 3000);
});

Promise
  .allSettled([p1, p2, p3])
  .then((param)=> {console.log(`then(${param.length}) : ${param}` ); console.log(param);} )
  .catch(error =>  console.log(`ERROR : ${error}`)); 

// 응답값
then(3) : [object Object],[object Object],[object Object]
(3) [{}, {}, {}]
0 : {status: 'fulfilled', value: 'p1 성공'}
1 : {status: 'fulfilled', value: 'p2 성공'}
2 : {status: 'rejected', reason: Error: p3 실패 at <anonymous>:11:27}

Promise.race()

resolve, reject와 관계 없이 처음 한번은 then()이 실행 된다.

let p1 = new Promise((resolve, reject) => {
  setTimeout(() => resolve("p1 성공"), 1000);
});

let p2 = new Promise((resolve, reject) => {
  setTimeout(() => resolve("p2 성공"), 2000);
});

let p3 = new Promise((resolve, reject) => {
  setTimeout(() => resolve("p3 성공"), 3000);
  //setTimeout(() => reject(new Error("p3 실패")) , 3000);
});

Promise
  .race([p1, p2, p3])
  .then((param)=> console.log(`then : ${param}`))
  .catch(error => console.log(`ERROR : ${error}`)); 

// 모두 성공 반환시
// param 은 p1,p2,p3의 resolve 인수를 모두 배열로 반환 ['p1 성공', 'p2 성공', 'p3 성공']

// 한건 이상의 Fail판환시
// catch 로직으로 진입함 ERROR : Error: p3 실패 

async / await

최근 나온 문법으로 Promise 사용시 발생하는 Promise 지옥 또는 Callback 지옥 을 해결할 수 있는 비동기 처리 문법 입니다.
async, await 는 함수 안에서만 동작하는 제약 사항이 있으며, await를 통해서 Promise 반환 값을 전달 받을 수 있습니다.

코드 가독성을 개선한 방법이 async/await이지만 동작의 큰 흐름적 방향성은 유사 합니다. Promise는 catch()를 통해서 에러 처리를 지원하며, async/await 별도의 에러 처리 메소드를 제공하지 않고 일반 에러 처리 로직인 try{ ... }cathc(e){ ... }를 통해 지원 합니다.

사용 예

//익명 함수 형태
(async () => {
    let promise = new Promise((resolve, reject) => {
        setTimeout(() => resolve("p 성공"), 1000);
        //setTimeout(() => reject(new Error("p 실패")) , 1000);
    });

    try {
        let result = await promise;
        console.log(result);
    } catch (err) {
        console.error(err);
    }
})();


//명시적 함수 생성
async function ex(){
    let promise = new Promise((resolve, reject) => {
        setTimeout(() => resolve("p 성공"), 1000);
        //setTimeout(() => reject(new Error("p 실패")) , 1000);
    });

    try {
        let result = await promise;
        console.log(result);
    } catch (err) {
        console.error(err);
    }
};
ex();