[JavaScript] Asynchronous(비동기) 코드와 Promise

허션·2025년 10월 3일

공부

목록 보기
12/13

Synchronous callback - 위에서 아래로 순차적 실행

코드 순서가 작업 순서와 마찬가지. 순차적으로 call stack(first in, last out)에 넣어지고, 넣어지자마자 바로바로 실행됨

call stack 1

call stack 2

Asynchronous Callback - 비동기 처리 함수

→ fetch, setTimeout 과 같은 Web API에서 제공되는 비동기 처리 함수를 이용할 수 있다.

ex.

setTimeOut(callback, milliseconds)
  • setTimeOut : 실행될 callback function을 지정된 시간 후에 실행되도록 예약(queue)하는 함수. → 기존의 프로그램 흐름에서 벗어나 asynchronous하게 실행됨.
    call stack 1 task queue가 비어있는지 감지하고 callback을 call stack으로 이동하는 것은 javascript의 event loop의 역할.

⇒ fetch와 같은 API를 사용할 시, API request에 대한 response는 asynchronous할 것.

(서버에 요청을 보내면 요청이 올 때까지 시간이 좀 걸림 → 요청이 전부 수행될때까지 브라우저를 무작정 멈추어 둘 수는 없으므로, 서버의 요청이 도착하면 데이터 처리 코드를 실행하도록 aynchronous한 방식으로 코드를 작성해야 한다.)

callback pattern :

  • callback : function의 파라미터로 다른 function definition을 넣는 것.

    → callback pattern : asynchronous한 코드를 작성하기 위해 callback을 이용할 수 있다.

  • success callback : 함수 수행 성공 이후 실행되는 asynchronous callback

    • e.g. 아래 welcomeUser에서 두 번째 인자로 전달해주는 callback은 success callback이다.
      // usage
      welcomeUser("Sam", () => {
      	console.log("Done welcoming user");
      });
      
      // definition
      const welcomeUser = (name, callback) => {
      	setTimeOut(() => {
      		console.log(`Welcome ${name}`);
      		callback(); //success callback
      		}, 1_000);
      }
  • error callback : 함수 수행에 모종의 이유로 실패할 시 실행되는 asynchronous callback
    (e.g. reason => {console.error(reason);), 주로 success callback 다음 순서의 인자로 입력된다.
  • e.g. success callback이 연산의 결과를 parameter로 받는 경우
    export const sumGrades = (grades, callback) => {
    	// simulate expensive operation
    	setTimeout(() => {
    		const sum = grades.reduce((total, current) => total+current, 0);
    		if (callback) {
    			callback(sum); // call success callback with the sum
    			}
    		}, 1_000);
    	}
    const calculateSum = (grades) => {
    	sumGrades(grades, result => {
    		console.log(`The sum is : ${result}`);
    		});
    	}
    	calculateSum([18,10]);

Callback pattern vs Promise :

Promise: Promise의 결과에 따라 callback을 수행할 수 있게 하는 wrapper의 일종이라고 생각할 수 있음.

→ Promise를 이용하여 asynchronous code를 작성하는 것이 보다 직관적인 syntax

e.g.1. callback을 이용할 경우 생길 수 있는 callback hell ..

callback hell

  • 비교 예시
    callback pattern :
    sumTemperatures(temperatures, value => {
    	console.log(value);
    }, reason => {
    	console.error(reason);
    });
    → promise : .then과 .catch() 로 함수 실행 순서를 정해줄 수 있으므로 callback 인자 순서를 상관하지 않아줘도 됨, 보다 직관적.
    sumTemperatures(temperatures)
    	.then(value => {
    		console.log(value);
    	})
    	.catch(reason => {
    		console.error(reason);
    	});

그래서 promise가 뭔데?

Promises:

  • 비유 : 키오스크에서 음식 주문 후 받는 번호표가 Promise object와 비슷하다고 볼 수 있다. (주문 후 아직 음식이 없지만, 음식이 될 수 있는 것.)

    주문이 그냥 배달될수도(resolve) 취소될수도(rejected) 있고, .then(), .catch() method로 배달과 취소 각각의 경우를 다룰 수 있다. (e.g. then() : 음식이 됨, catch() : 음식이 되지않음 ..)

  • .then() , .catch() : Promise를 반환하는 함수에만 쓸 수 있는 method. fetch와 getUserMedia 등 여러 WebAPI가 promise를 활용하기 때문에 중요하다.
    → 각각 Promise 실행 성공/실패의 경우 무엇을 할지를 다룬다.

  • e.g. 아래 wait() 함수 : 인자로 기다릴 시간을 받고, promise를 결과로 반환

    const wait = milliseconds => {
    	return new Promise(resolve => {
    			setTimeout(() => {
    					resolve();
    				}, milliseconds);
    			}, reject => {
    				reject("function rejected");
    			});
    }

    → 반환되는 Promise의 파라미터 :

    • resolve : success callback과 같은 역할, wait.then(resolve)와 같은 형식으로 전달됨.

    • reject : error callback과 같은 역할, wait.catch(reject)와 같은 형식으로 전달된다.

      ⇒ 이때 .then()과 .catch()는 wait이 반환하는 Promise에 불러져서 promise에 callback을 전달해주는 역할을 함.

      wait(1_000).then(() => {
      	console.log("waited 1 second");
      })
      .catch(() => {
      	console.log("rejected");
      });
      ;
  • Promise의 state
    • pending : promise가 외부 함수에 의해 반환되었지만 아직 실행되지 않은 상태 (e.g. 성공/실패 결과가 정해지지 않음)

    • fulfilled : promise 실행이 성공하여 이에 따른 success callback의 실행까지 완료됨

      → promise가 fulfilled 상태가 되면 .then(callback)이 실행된다

    • rejected : promise가 성공적으로 실행되지 못했을 때의 state (ex. API 응답이 네트워크 이슈로 성공적으로 수령되지 못했을 때)

      → 이때 실행할 에러 처리용 callback을 .catch(callback)으로 전달해줌으로써 에러를 다루는 로직을 구현할 수 있음(에러메시지 띄우기 등)

      e.g. :

      console.log(wait(1_000));
      // console : Promise {<pending>}
      const result = wait(1_000);
      console.log(result);
      // console : Promise {<pending>}

      → Promise의 callback 실행을 위해서는 1초가 지나야 하기 때문에, 함수를 부르고 난 직후의 출력은 상태를 보여준다.

      const result = wait(1_000);
      console.log(result);
      // Promise {<pending>}
      
      result.then(() => {
      	console.log(result);
      	// Promise {<fulfilled: undefined>}
      });
      console.log(result);
      // Promise {<pending>}

      → wait()이 반환하는 promise가 result에 담겨있으므로, result에 .then을 사용할 수 있다.

      ⇒ result.then() 안의 console.log(result)만 promise가 fulfilled 상태가 된 이후 실행되므로, 출력값은 아래와 같다

      Promise {<pending>}
      Promise {<pending>}
      Promise {<fulfilled: undefined>}

Promise 만들기

  • promise를 사용하기 위해서는 Promise를 반환하는 함수를 정의해야 한다.

    → 일반적인 형식 :
    const function = (parameter) => {
    										// Executor!
    	return new Promise((resolve, reject) => {
    		/* 함수에서 실행할 statement들 */
    		resolve(); // promise가 성공적이면 실행될 .then(callback)
    		reject(); // promise가 실패하면 실행될 .catch(callback)
    	});
    }
    이때 Promise가 초기화될때 받는 argument를 executor(()⇒{})이라고 한다.
    Promise가 실행 완료되었을 때(i.e, fulfilled state가 되었을 때) 실행하는 callback인 resolve()함수는 executor의 첫번째 인자이다.
    Promise가 실행 실패했을 때(i.e, rejected state가 되었을 때) 실행하는 callback인 reject()함수는 executor의 두번째 인자이다.
    즉 Promise의 argument인 executor의 argument ()에 사용할 callback들(resolve, reject)이 들어가고, body {} 에는 그 callback들을 promise로 활용하는 function의 내용이 있다.

Resolving data

  • Promise가 성공적으로 실행되어 ‘fulfilled’ state가 되었을 때, promise가 “resolve”(해결) 되었다고 한다. → ”*promises can resolve with data”* : promise는 외부 함수(자신을 반환하는 함수)가 다루는 data를 자신 내부에서 활용할 수 있다. → 즉, .then(resolve)으로 전달되는 resolve 함수는, argument로 외부에서 전달될 data를 넣어줌으로써 이를 활용할 수 있다. ⇒ resolve(data) e.g. 실제 구현의 경우 외부 API에서 반환되는 data를 활용하는 코드를 작성할 수 있다.
  • 예시 : 사용자가 입력한 milliseconds 만큼 기다렸다가, 기다린 시간을 초 단위로 console에 보여주는 resolve callback을 실행하는 wait 함수의 구현
    const wait = milliseconds => {
    	return new Promise(resolve => {
    		setTimeout(() => {
    		resolve(milliseconds / 1_000);
    		}, milliseconds);
    	});
    }
    wait(2_000).then((data) => {
    	console.log(data); // 2
    });

Rejecting data

executor의 두번째 callback argument는 Promise가 rejected state일때 실행되는 .catch(callback)과 동일한 reject() 함수이다.

resolve()와 같이 reject()도 함수의 data를 전달받아 이용할 수 있다.

  • e.g. 항상 reject되는 함수 :
    const alwaysFail = () => {
    	return new Promise((resolve, reject) => {
    		reject("Failed. That's the only thind I do.");
    	});
    }
    alwaysFail()
    	.then(() => {
    		// never called
    }).catch(data => {
    		console.error(data);
    });

Promise chaining

resolve, reject와 같이 promise에서 실행하는 callback들도 Promise를 반환할 수 있다. 이 경우 다음과 같은 promise chaining이 가능하다.

  • fetch example
    fetch("some-url")
    	.then(response => response.json())
    	.then(data => {
    		console.log(data);
    	});

Promise.finally() :

  • .then()은 promise가 성공적으로 실행돼서 fulfilled 상태가 되면 수행하고, .catch()는 promise 실행에 실패해서 rejected 상태가 되면 실행한다.
    ⇒ .finally()는 fulfilled든 rejected든 상관없이 pending 상태에서 벗어나면 callback을 실행한다.
    ⇒ .then()과 .catch()에서 공통적으로 실행하는 statement가 있으면, .finally()로 깔끔하게 넣어줄 수 있음. (e.g. console.log(”finished”); )

  • ex. 로딩을 시작하고 끝내는 startLoader(); stopLoader() 함수를 가상의 API 함수 getWeatherIn()의 시작과 끝에 각각 두고 싶을 때 (기상정보가 반환됐을 때 loader을 안보이게 하는 등)
    startLoader();
    getWeatherIn("Amsterdam")
    	.then(data => {
    			console.log(data);
    		})
    	.catch(error => {
    			console.error(error);
    		})
    	.finally(() => {
    			stopLoader();
    	});

Promise.all() & Promise.any()

Promise.all() :

  • method .all()이 인자로 받는 모든 promise가 fulfilled일때, .all()을 부른 Promise 또한 fulfilled 상태가 되고, 그 외에는 reject 상태가 된다.
    → 여러 개의 promise를 동시에 실행하고 모든 promise가 성공할 때까지 기다릴 수 있다.
  • e.g. Javascript file과 CSS file을 동시에 불러오는데, 두 파일 모두 불러오기 전까지 component를 render하기 싫을 때.
    Promise.all([promise1, promise2]).then(values => {
    		console.log(values);
    }).catch(error => {
    		console.error(error);
    });

Promise.any() :

  • .all()과 사용법이 비슷하지만, 인자로 받은 모든 promise가 아니라 그 중 하나의 promise라도 성공하면 가장 처음 성공하는 promise의 data를 받아온다.
  • e.g.
    Promise.any([promise1, promise2]).then(value => {
    	console.log(value);
    }).catch(error => {
    	console.error(error);
    });
profile
다시해보자.

0개의 댓글