Promise와 Generator 함수를 이용한 비동기처리
비동기 처리에는 다양한 방법들이 있다.
비동기 처리에 공부하기 앞서 우리가 알아야 할 것은 비동기 콜백이 비동기지 콜백은 무조건 비동기가 아니다.
한번 비동기는 영원한 비동기이다. 비동기는 동시가 아니라 순서의 문제다. 비동기 코드는 코드순서랑 실제 실행 순서랑 다르다.
-
callback(콜백) 함수
코드양이 많아지면 가독성이 떨어지고 복잡해진다.
즉, 콜백지옥(callback hell)을 만든다.
콜백지옥이란 콜백 함수를 익명 함수로 전달하는 과정에서 또 다시 콜백 안에 함수 호출이 반복되어 코드의 들여쓰기 순준이 감당하기 힘들 정도로 깊어지는 현상을 말한다.
//1 setTimeout( (x) => { let result = x; console.log(result); // 10 //2 setTimeout( (x)=>{ result *= x; console.log(result); // 200 //3 setTimeout( (x)=>{ result *= x; console.log(result) // 6000 //4 setTimeout( (x)=>{ result *= x; console.log(result) // 240000 }, 1000, 40 ) }, 1000, 30 ) }, 1000, 20 ); }, 1000, 10 );
-
Promise로 변환
비동기를 제어하는 방법(콜백 지옥 탈출)으로 Promise가있다.
Promise란 실행은 바로 하되, 결괏값을 나중에 원할 때 쓸 수 있는 것이다.
Promise를 사용해서 구현을 하면 좀 더 가독성 있게 편리하게 복잡하지 않게 필요한 만큼 작성해나갈 수 있다.
new Promise()
호출하여 대기 상태가 된다.- 이때, 콜백 함수를 선언할 수 있고 인자는
resolve, reject
가된다. - 콜백 함수내에서 처리할거 처리한후에
resolve()
메서드를 호출하면 이행상태가 된다. - 즉, 성공이면 리턴 값을
then()
이 받아서 계속 처리 수행한다.promise
를 사용하면callback
함수보다 좀 더 수월해진다.
new Promise((resolve, reject) => { setTimeout( (x)=>{ let result = x; console.log(result); resolve(result); }, 1000, 10 ); }) .then((result) => { return new Promise((resolve, reject) => { setTimeout( (x)=>{ result*=x; console.log(result); resolve(result); }, 1000, 20 ); }); }) .then((result) => { return new Promise((resolve, reject)=> { setTimeout( (x)=>{ result*=x; console.log(result); resolve(result); }, 1000, 30 ); }); }) .then((result) => { return new Promise((resolve, reject)=> { setTimeout( (x)=>{ result*=x; console.log(result); resolve(result); }, 1000, 40 ); }); });
프로미스로 감싸서 원할 때 결과값을
resolve
해주면된다.const p = new Promise((resolve, reject) => { setTimeout(() => { a = 5; console.log(a); resolve(a); }, 0); }); // 딴짓 // 딴짓 // 딴짓 // 딴짓 console.log('Hi'); // 딴짓 // 딴짓 // 딴짓 p.then((result) => { console.log('result', result); }); // Hi // 5 // result 5
function delayP(ms) { return new Promise((resolve, reject) => { setTimeout(resolve, ms); }); };
동시가 아니라 순서의 문제다. 비동기 코드는 코드순서랑 실제 실행 순서랑 다르다.
setTimout
의 특정 조건은 시간,초가 지나면이고promise
의 특정 조건은resolve
함수가 호출되면이다.
setTimeout(() => { console.log('a'); }, 0); setTimeout(() => { console.log('b'); }, 1000); setTimeout(() => { console.log('c'); }, 2000); Promise.resolve().then(() => { console.log('p'); }); //p //a //b //c
Promise.all
을 사용하면 6명한테 송금하는데 5명이 성공했어도p4
만 실패를 하면catch
로 가는데catch
에서는 몇번 째가 실패했는지 알 수 가 없다. 그렇게 되면 6개중에 5개가 성공했어도 전부 다 취소한 다음에 다시 한번 시도해야한다.const p1 = axiose.post('서버주소1'); // 송금 const p2 = axiose.post('서버주소1'); // 송금 const p3 = axiose.post('서버주소1'); // 송금 const p4 = axiose.post('서버주소1'); // 실패 const p5 = axiose.post('서버주소1'); // 송금 const p6 = axiose.post('서버주소1'); // 송금 Promise.all([p1, p2, p3, p4, p5, p6]).then((result) => { }).catch((error)=> {}); // 여기서 catch는 p에대해서의 catch가아니라 Promise.all([p1, p2, p3, p4, p5, p6]).then((result) => {})에 대한 catch이다.
위의 문제점을 보완한 것이
Promise.allSettled
이다.Promise.allSettled([p1, p2, p3, p4, p5, p6]).then((result) => { // 실패한 것만 필터링해서 다시 시도 }).catch((error)=>{});
프로미스를 리턴하면 그 프로미스가
resolve
값이 리턴이되고 프로미스가 아닌 일반 값을 리턴하면 그 값이 그대로 다음으로 넘어간다.p.then((result) => { console.log('result', result); return 1; }).then((result) => { console.log(result); // 1 }).then((result) => { console.log(result) //undefined }).then(() => { // 에러났다. }).then(() => { }).then(() => { }).then(() => { }).catch(() => { // 에러로 이동 }).finally(() => { });
Promise + async/await
- ES2017에서 추가 된 async/await 문법을 이용한 방법이 있다.
- 비동기 작업을 수행하고자 하는 함수 앞에
async
를 표기하고, 함수 내부에서 실질적인 비동기 작업이 필요한 위치마다await
을 표기하는 것만으로 뒤의 내용을Promise
로 자동 전환하고 해당 내용이resolve
된 이후에야 다음으로 진행된다.
async function a() { console.log('2'); // async 함수는 마지막에서 끝나는게 아니라 첫번째 await전에서 끝난다. const a = await 1; // 정확하게는 동기부분이 await 전에서 끝난다. console.log('4'); console.log('a', a); // 여기서부터 비동기부분 console.log('hmm'); await null; const b = await Promise.resolve(1); console.log('b', b); return b; } // async 함수는 여기서 끝나는게 아니다. console.log('1'); a().then((result) => { console.log(result); }).then((result2) => { console.log(result2); }); console.log('3'); //1 //2 //3 //4 //a 1 //hmm //b 1 //1 //undefined
-
async
를promise
로 변환하기변환하는 이유? 코드 분석하기 훨씬 쉽다.
-
분석 방법
-
await
이 기준이다.await
=>then
이라고 생각한다. -
async
오른쪽에서 왼쪽으로promise
에서는 왼쪽에서 오른쪽, 위에서 아래로 읽는다. -
첫번째
await
이Promise
나axios.get()
이 아니면Promise.resolve(1)
이렇게 프로미스화를 시켜줘야then
을 붙일수있다. -
await
에서 대입하는 변수를then((a)
에 넣어준다. 그러면a
가 그대로 정확하게1
이 된다. -
await
과await
사이를 전부다 넣어주면된다. 여기서 오른쪽부터 코드를 읽어야한다.await붙으면 다 비동기로 처리한다.
-
Promise.resolve(1) .then((a) => { console.log('4'); console.log('a', a); console.log('hmm'); return null; }) .then(() => { return Promise.resolve(1); }) .then((b) => { console.log('b', b); return b; }) .then((result) => { console.log(result); }) .then((result2) => { console.log(result2); }); console.log('1'); console.log('2'); console.log('3'); //1 //2 //3 //4 //a 1 //hmm //b 1 //1 //undefined
- 무지성
await
연달아쓰기 금지!
function delayP(ms) { return new Promise((resolve, reject) => { setTimeout(resolve, ms); }); }; async function a() { await delayP(3000); // 3 await delayP(6000); // 6 await delayP(9000); // 9 } //토탈 18초 async function b() { const p1 = await delayP(3000); const p2 = await delayP(6000); await Promise.all([p1, p2]); // 6 await delayP(9000); //9 } //토탈 15초 // 여기서 Promise.all 의 역할은 p1, p2의 결괏값을 한번에 묶어서 나중에 쓸 수 있게 해주는것이다.
Generator로 변환
const calc = (x, y) => { setTimeout( ()=>{ iter.next(x*y); }, 1000 ) } function* testGen() { const a = yield calc(1,10); console.log(a); const b = yield calc(a, 20); console.log(b); const c = yield calc(b, 30); console.log(c); const d = yield calc(c, 40); console.log(d); }; const iter = testGen(); iter.next(); //iter.next(10) 10 //iter.next(200) 200 //iter.next(6000); 6000 //iter.next(240000); 240000
참고: 제로초 강의
댓글남기기