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

허션·어제
0

공부

목록 보기
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. fetchgetUserMedia 등 여러 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인 executorargument ()사용할 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개의 댓글