4 분 소요

비동기 처리에는 다양한 방법들이 있다.

비동기 처리에 공부하기 앞서 우리가 알아야 할 것은 비동기 콜백이 비동기지 콜백은 무조건 비동기가 아니다.

한번 비동기는 영원한 비동기이다. 비동기는 동시가 아니라 순서의 문제다. 비동기 코드는 코드순서랑 실제 실행 순서랑 다르다.

  1. 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
    );
    
  2. 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
    
    • asyncpromise로 변환하기

      변환하는 이유? 코드 분석하기 훨씬 쉽다.

    • 분석 방법

      • await이 기준이다. await => then이라고 생각한다.

      • async 오른쪽에서 왼쪽으로 promise에서는 왼쪽에서 오른쪽, 위에서 아래로 읽는다.

      • 첫번째 awaitPromiseaxios.get()이 아니면 Promise.resolve(1) 이렇게 프로미스화를 시켜줘야 then을 붙일수있다.

      • await에서 대입하는 변수를 then((a)에 넣어준다. 그러면 a가 그대로 정확하게 1이 된다.

      • awaitawait 사이를 전부다 넣어주면된다. 여기서 오른쪽부터 코드를 읽어야한다.

        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
    

참고: 제로초 강의

댓글남기기