ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Javascript] 프로미스, 콜백, callback, promise, all, allSettled, any, race
    웹/JavaScript 2023. 1. 14. 14:15

    콜백 지옥 callback hell

    setTimeout(() => {
    console.log(1);
    setTimeout(() => {
    console.log(2);
    setTimeout(() => {
    console.log(3);
    setTimeout(() => {
    console.log(4);
    setTimeout(() => {
    console.log(5);
    }, 500);
    }, 500);
    }, 500);
    }, 500);
    }, 500);

    💡 연속적으로 비동기 코드를 써야 하는 경우

    • 위와 같이 콜백 함수 안에 또 다른 콜백 함수를 넣어야 하는 상황 발생 - 콜백 지옥
    • 횟수가 많을수록 가독성도 낮아지고 직관성이 떨어짐
    • 실전에서는 더더욱 복잡하고 난해해짐

     

    🏃🏃🏃 릴레이 예제

    • 철수, 영희, 돌준, 정아, 길돈이 치례로 이어달리기는 하는 코드
    • ⭐ 각 골인시간 기록 - 이전 콜백함수의 결과가 다음 콜백함수로 넘겨져 축적됨
    • 한 주자라도 데드라인(밀리초)을 넘기면 실패 - 주자마다 다른 실패 메시지 출력
    • 완주 실패시 😢 완주 실패 - ${전체 소요시간} 출력
    • 실패든 성공이든 마지막에 - - 경기 종료 - - 출력

     

    const DEADLINE = 1400;
    function relayRun (name, start, nextFunc, failMsg) {
    console.log(`👟 ${name} 출발`);
    const time = 1000 + Math.random() * 500;
    setTimeout(() => {
    if (time < DEADLINE) {
    console.log(`🚩 ${name} 도착 - ${(start + time)/1000}`);
    nextFunc?.(start + time);
    } else {
    console.log(failMsg);
    console.log(`😢 완주 실패 - ${(start + time)/1000}`);
    }
    if (time >= DEADLINE || !nextFunc) {
    console.log('- - 경기 종료 - -');
    }
    }, time);
    }

     

    relayRun('철수', 0, start1 => {
    relayRun('영희', start1, start2 => {
    relayRun('돌준', start2, start3 => {
    relayRun('정아', start3, start4 => {
    relayRun('길돈', start4, null, '아아, 아깝습니다...');
    }, '정아에게 무리였나보네요.');
    }, '돌준이 분발해라.');
    }, '영희가 완주하지 못했네요.');
    }, '철수부터 광탈입니다. ㅠㅠ');




     

    프로미스 promise

    • (보통 시간이 걸리는) 어떤 과정 이후 주어진 동작을 실행할 것이란 약속
    • 중첩된 비동기 코드를 직관적이고 연속적인 코드로 작성할 수 있도록 함
    • 👉 MDN 문서 보기



    const borrow = 20;
    // 빌린 돈의 10%를 더해 값겠다는 약속
    // reject는 지금 사용하지 않음
    const payWith10perc = new Promise((resolve, reject) => {
    resolve(borrow * 1.1);
    });
    payWith10perc
    .then(result => {
    console.log(result + '만원');
    });

    생성자 Promise - 👉 MDN 문서 보기

    • 새로운 약속을 하는 코드
    • 인자로 받는 콜백함수의 첫 번째 인자 resolve ( 이름은 관례 ) - 약속 이행 성공시, 반환할 값 넣어 실행

    프로미스 인스턴스 ( 만들어진 약속 ) 의 then 메서드

    • resolve를 통해 ( 약속대로 ) 반환된 결과를 인자로 하는 콜백 함수를 넣음
    • ⭐ 또 다른 프로미스를 반환 - 체이닝 가능
    • 👉 MDN 문서 보기 - ( 추가 인자 )



    const borrow = 20;
    const payWith10perc = new Promise((resolve, reject) => {
    // 💡 내부에서 비동기 코드 사용
    setTimeout(() => {
    resolve(borrow * 1.1);
    }, 1000); // 1초 후 갚겠음
    });
    // ⚠️ 콘솔에서 분리해서 실행하면 안 됨!
    // 프로미스가 생성되는 순간부터 시간 경과
    payWith10perc
    .then(result => {
    console.log(result + '만원');
    });
    • 일반적으로 내부에 비동기 코드를 사용
    • 시간이 소모되는 비동기 과정 후 ~를 반환하겠다는 약속



    const borrow = 20;
    const payWith10perc = new Promise((resolve, reject) => {
    setTimeout(() => {
    if (Math.random() < 0.5) {
    // 💡 돈을 값을 수 없게 되었을 때
    reject('사업 망함'); // 보통 실패사유나 관련 설명을 넣음
    }
    resolve(borrow * 1.1);
    }, 1000); // 1초 후 갚겠음
    });
    payWith10perc
    .then(result => {
    console.log(result + '만원');
    }
    // 💡 두 번째 인자로 reject를 받는 콜백을 넣을 수 있지만
    // 아래처럼 catch로 진행하는 것이 더 깔끔함
    )
    .catch(msg => {
    console.error(msg);
    })
    .finally(() => {
    console.log('기한 종료');
    });

    생성자 Promise

    • 인자로 받는 콜백함수의 두 번째 인자 reject ( 이름은 관례 ) - 약속 이행 실패시, 반환할 값 넣어 실행
    • reject가 실행되면 resolve는 무시됨

    프로미스 인스턴스의

    • catch 메서드 : reject를 통해 ( 실패로 인해 ) 반환된 결과를 인자로 하는 콜백 함수를 넣음
    • finally 메서드 : 성공하든 실패하든 실행할 콜백 함수 - 필요할 때만 사용
    • then과 더불어 메서드 체이닝으로 사용



    // ⭐ then은 연속적으로 메서드 체이닝 가능
    new Promise((resolve) => {
    resolve(2);
    })
    .then(i => i * 4)
    .then(i => i - 3)
    .then(i => i ** 2)
    .then((i) => {
    console.log(i);
    });



    💰 10% 이자, 채무자 파산가능성 10%, 5번 빌려주기

    // 빌린 금액으로 약속을 하는 함수
    function moneyLend (borrow) {
    return new Promise((resolve, reject) => {
    console.log(`채무 ${borrow}만원`);
    setTimeout(() => {
    if (Math.random() < 0.1) {
    reject('채무자 파산');
    }
    resolve(borrow * 1.1);
    }, 1000);
    });
    }

     

    moneyLend(20)
    .then(result => moneyLend(result))
    .then(moneyLend) // 인자를 하나 받아서 그대로 쓰므로
    .then(moneyLend) // 이렇게 줄여버릴 수 있음
    .then(moneyLend)
    .then(result => {
    console.log(`💰 반납 ${result}만원`);
    })
    .catch(msg => {
    console.error(msg);
    })
    .finally(() => {
    console.log('- - 대금업 종료 - -');
    });




     

    🏃🏃🏃 릴레이 예제 프로미스로 구현

    const DEADLINE = 1400;
    function getRelayPromise (name, start, failMsg) {
    console.log(`👟 ${name} 출발`);
    // 💡 랜덤 시간만큼 달리고 결과를 반환하겠다는 약속을 만들어 반환
    return new Promise((resolve, reject) => {
    const time = 1000 + Math.random() * 500;
    setTimeout(() => {
    if (time < DEADLINE) {
    console.log(`🚩 ${name} 도착 - ${(start + time)/1000}`);
    resolve(start + time);
    } else {
     
    console.log(failMsg);
    reject((start + time) / 1000);
    }
    }, time);
    })
    }

     

    getRelayPromise('철수', 0, '철수부터 광탈입니다. ㅠㅠ')
    .then(result => {
    return getRelayPromise('영희', result, '영희가 완주하지 못했네요.');
    })
    .then(result => {
    return getRelayPromise('돌준', result, '돌준이 분발해라.');
    })
    .then(result => {
    return getRelayPromise('정아', result, '정아에게 무리였나보네요.');
    })
    .then(result => {
    return getRelayPromise('길돈', result, '아아, 아깝습니다...');
    })
    .catch(msg => {
    console.log(`😢 완주 실패 - ${msg}`);
    })
    .finally(() => {
    console.log('- - 경기 종료 - -');
    });
     

     

    여러 프로미스를 병렬 처리하기 위한 Promise의 정적 메서드들

    // 다섯 주자들이 동시에 질주
    // 데드라인(밀리초) 안에 들어오지 못하면 탈락
    let DEADLINE = 1450;
    function getRunPromise (name) {
    return new Promise((resolve, reject) => {
    const time = 1000 + Math.random() * 500;
    setTimeout(() => {
    if (time < DEADLINE) {
    console.log(`🚩 ${name} 도착 - ${(time)/1000}`);
    resolve({name, time});
    } else {
    reject((`${name} 시간초과`));
    }
    }, time);
    });
    }

     

    console.log(
    '철수,영희,돌준,정아,길돈'
    .split(',')
    .map(getRunPromise)
    );



    1. all - 👉 MDN 문서 보기

    • 프로미스의 배열을 받아 동시에 진행
    • 모두 성공하면 resolve된 값들을 배열로 반환 - then으로 받음
    • 하나라도 실패하면 catch 실행
    // 한 명이라도 탈락하면 전체 탈락
    Promise.all(
    '철수,영희,돌준,정아,길돈'
    .split(',')
    .map(getRunPromise)
    )
    .then(console.log)
    .catch(console.error)
    .finally(() => {
    console.log('- - 경기 종료 - -');
    });

     

    // 성공시 탑3 표시
    Promise.all(
    '철수,영희,돌준,정아,길돈'
    .split(',')
    .map(getRunPromise)
    )
    .then(arr => {
    return arr.sort((a, b) => {
    return a.time - b.time
    })
    .map(({name}) => name)
    .splice(0, 3)
    .join(', ');
    })
    .then(top3 => {
    console.log(`탑3: ${top3}`);
    })
    .catch(console.error)
    .finally(() => {
    console.log('- - 경기 종료 - -');
    });



    2. allSettled - 👉 MDN 문서 보기

    • 주어진 프로미스들의 결과를 배열로 출력
    • 실패 유무 관계없이 then으로 배열 반환
    Promise.allSettled(
    '철수,영희,돌준,정아,길돈'
    .split(',')
    .map(getRunPromise)
    )
    .then(console.log)
    // ⚠️ catch는 동작하지 않음
    .finally(() => {
    console.log('- - 경기 종료 - -');
    });

     

    Promise.allSettled(
    '철수,영희,돌준,정아,길돈'
    .split(',')
    .map(getRunPromise)
    )
    .then(arr => {
    return {
    succ: arr.filter(result => {
    return result.status === 'fulfilled'
    }),
    fail: arr.filter(result => {
    return result.status === 'rejected'
    })
    }
    })
    .then(res => {
    res.succ.sort((a, b) => {
    return a.value.time - b.value.time;
    });
    console.log(
    `완주: ${res.succ.length}명 (1등: ${res.succ[0].value.name})`
    );
    console.log(
    `탈락: ${res.fail.length}`
    );
    })
    .finally(() => {
    console.log('- - 경기 종료 - -');
    });



    3. any - 👉 MDN 문서 보기

    • 가장 먼저 성공한 프로미스의 결과를 then으로 반환
    • 모두 실패시 오류 발생
    DEADLINE = 1050;

     

    Promise.any(
    '철수,영희,돌준,정아,길돈'
    .split(',')
    .map(getRunPromise)
    )
    .then(console.log)
    // ⚠️ 모두 실패해도 catch는 동작하지 않음
    .finally(() => {
    console.log('- - 경기 종료 - -');
    });



    4. race - 👉 MDN 문서 보기

    • ⭐ 성공이든 실패든 첫 결과물 then 또는 catch로 반환
    // 다섯 주자들이 선택한 도착지로 질주
    // 도착지에 '꽝'이 있으면(50% 확률) 실패
    function getBombRunPromise (name) {
    return new Promise((resolve, reject) => {
    const time = 1000 + Math.random() * 500;
    setTimeout(() => {
    console.log(`🚩 ${name} 도착 - ${(time)/1000}`);
    if (Math.random() < 0.5) {
    resolve((`🙌 ${name} 승리!`));
    } else {
     
    reject((`💣 ${name} 꽝!`));
    }
    }, time);
    });
    }

     

    Promise.race(
    '철수,영희,돌준,정아,길돈'
    .split(',')
    .map(getBombRunPromise)
    )
    .then(console.log)
    .catch(console.error)
    .finally(() => {
    console.log('- - 경기 종료 - -');
    });
     
Designed by Tistory.