on
[JS 입문] 6. 자바스크립트의 비동기 동작
[JS 입문] 6. 자바스크립트의 비동기 동작
자바스크립트가 실제로 동작되는 (특히 브라우저 상에서) 환경에선 비동기적으로 동작되는 경우가 많다. 예를 들면 타이머와 서버와의 통신, 이벤트 등이 있다.
사용자가 브라우저를 통해 이런저런 요소들을 실행하면 이런 것들이 실행되며 비동기적으로 동작되는 것이 좋다. (왜냐하면 어떤 동작을 할때 그 동작이 끝날때까지 페이지가 아예 멈춰있는 것은 사용자 입장에서 좋지 않기 때문이다.)
1. 콜백
자바스크립트가 제공하는 가장 일반적인 비동기 동작의 처리방법은 콜백함수를 사용하는 것이다.
실제로 이런 코드는 효용성이 별로 없지만 비슷한 상황이 많다.
let num = 1; setTimeout(function() { num += 1; }, 1000); console.log(num);
위 코드에서 num은 결과적으로는 2가 되겠지만 콘솔에는 1이 찍힌다. 왜냐하면 setTimeout 내의 동작은 setTimeout이 실행되고 1초 뒤에 내부 함수가 비동기적으로 실행되기 때문이다.
그래서 비동기적인 동작을 실행시킬때엔 비동기적인 동작이 실행되고 이후로 실행될 기능을 콜백함수로 넘기는 방법이 기본이다.
let num = 1; function callback(num) { console.log(num); } setTimeout(function() { num += 1; callback(num); }, 1000);
그런데 비동기적인 동작을 할 때마다 콜백함수를 호출하는 방법은 비동기 동작과 연관된 기능이 많을수록 콜백함수가 겹겹이 쌓여있는 모양새가 될 수도 있다. 보통 그렇게 과하게 콜백함수를 호출하는 코딩 패턴은 콜백지옥이라고 부른다.
2. Promise
Promise는 비동기 동작의 완료나 실패 (둘 중 하나는 반드시 나타내야 함)를 나타나는 객체이다. promise는 비동기 동작 내에 콜백 함수를 직접 집어넣는 것이 아닌 비동기 작업 결과에 따라 콜백 함수를 호출하는 방식을 사용한다.
promise의 가장 큰 장점은 단순 콜백을 사용할때에 비해 여러 동작을 엮는 것이 편하다는 것이다. 함수 내에서 함수를 선언하고 또 그 내에서 함수를 선언하는 것이 콜백지옥의 패턴인데 promise의 "chaining" 을 사용하면 메소드에 실행하고자 하는 함수들을 이어서 등록하면 가독성이 훨씬 좋아진다.
보통 비동기 동작을 Promise 생성자를 이용해 Promise 객체를 만들며 이 객체에 대해 동작이 실패하거나 성공할 시에 동작할 기능들을 콜백함수로 전달한다. 순차적으로 실행할 함수가 여러개라면 체이닝을 이용해 전달한다.
Promise 객체는 생성되고 난 후 비동기적으로 동작한다.
Promise 객체를 생성하는 기본 패턴
위의 코드를 Promise를 이용한 패턴으로 수정한 것이다.
let num = 1; // promise 객체 생성 let promise = new Promise( // Promise 생성자의 인수는 executer 함수하고 부르며 resolve와 reject 콜백함수 인자를 받는다. function(resolve, reject) { // 비동기 코드의 최종 실행 결과에 따라 반드시 두 함수 중 하나라도 호출하여야 한다. setTimeout(function() { num += 1; if (num == 2) { resolve(num); // 성공 } else { reject(num); // 실패 } }, 1000); } ); // then 메소드로 비동기 코드의 실행 결과에 대한 동작을 실행하기 위한 콜백함수를 전달한다. // 첫번째 인자는 성공시에 호출할 함수이며 두번째 인자는 실패시에 호출할 함수이다. promise.then(console.log, console.log); // then 메소드는 여러개 호출 가능하다. 처리해야 할 작업이 여러개라면 여러개 호출해도 된다. // 아래는 체이닝의 예시인데 체이닝 시엔 콜백함수로 전달되는 인수에 대한 값을 리턴해야 한다. promise.then(value => { console.log(value); return value; }).then(value => { console.log(value); return value; })
promise 객체는 세가지 상태를 가진다.
pending : 대기 상태
fulfilled : resolve() 함수가 호출되어 일이 끝난 상태
rejected : reject() 함수가 호출되어 일이 끝난 상태
promise 객체의 예외처리
promise 객체에 catch 메소드를 달아두면 executer 함수 내와 then 내에서 일어나는 에러들에 대해 일괄적으로 에러 처리를 할 수 있다.
new Promise(function(resolve, reject) { throw new Error(0) }) .catch((e)=>{console.log(e)}) .then(result => { if (result == 1) { throw new Error(result); } return result; }) .then(result => { if (result == 2) { throw new Error(result); } return result; }) .then(result => { if (result == 3) { throw new Error(result); } return result; }) .then(result => { if (result == 4) { throw new Error(result); } return result; }) .then(result => { if (result == 5) { throw new Error(result); } return result; });
위 코드에서 .catch((e)=>{console.log(e)}) 를 이용해 에러가 발생하면 콘솔에 출력하고록 했고 executer 내나 then 메소드 내에서 발생하는 에러를 모두 catch를 통해 처리할 수 있다.
자세한 동작은 https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Promise 를 참조!
3. async, await
async 키워드
async 키워드는 함수를 promise 함수로 변환해 주는 역할을 한다. Promise와는 좀 다르지만 async 키워드를 함수에 붙이면 그 함수는 promise 함수로 변환되어 함수를 실행하면 promise 객체가 반환된다.
let num = 1; // 비동기 함수 생성 let promise = async function() { num++; return num; } console.log("start"); promise().then(console.log); console.log("end");
위 코드는 start -> end -> 2 순서로 출력된다.
await 키워드
await 키워드는 비동기 함수의 결과값을 기다리게 해주는 키워드이다.
// promise 객체 생성 let promise = new Promise( function(resolve, reject) { setTimeout(function() { resolve(10); }, 1000); } ); // 비동기 함수 생성 let async_fun = async function() { let val = await promise; // promise 객체가 처리될때까지 기다린다. return val; } async_fun().then(console.log);
await 키워드는 프로그램 전체의 실행을 멈추고 기다리는 것이 아니라 비동기적으로 기다리기 때문에 효율적이다.
promise 객체의 then을 사용하는 것보다 가독성이 더 좋다.
단점은 await 키워드는 async 함수 내에서만 사용이 가능하다. (promise 객체 내에서도 사용 불가능하다.) 그래서 함수 내에서 await 키워드를 사용하는 것이 아니라면 즉시 실행되는 익명 함수로라도 만들어서 실행해야 한다. (최근에는 Top-level await을 지원하는 엔진도 있다고 한다.)
await이 기다리는 객체에서 발생하는 에러는 try ~ catch 문으로 처리한다.
// promise 객체 생성 let promise = new Promise( function(resolve, reject) { setTimeout(function() { // 일정한 확률로 성공하거나 실패함 let b = (Math.random() * 10) > 5 == 1; if (b) { resolve("ok"); } else { reject("error"); } }, 1000); } ); // 비동기 함수 생성 let async_fun = async function() { let val; try { val = await promise; } catch (e) { val = "error!"; } return val; }; // 비동기함수 즉시 실행 (async () => { result = await async_fun(); console.log(result); })();
from http://profq.tistory.com/56 by ccl(A) rewrite - 2021-09-16 11:01:26