
동기적 처리란 코드를 순서에 맞게 절차적으로 처리하는 것으로
특정 부분에서 시간이 오래걸리는 일을 수행하면 수행속도가 느려지는 단점이 있다.
function a(){
return 1;
}
function b(){
for(let i=0;i<100000000000000000;i++) // 시간이 굉장히 오래걸린다.
return a();
}
function c(){
return b();
}
const result =c();
console.log(result); // 1
call stack: c() -> b() -> a()
b 함수의 처리가 끝날 때까지 a는 호출되지 못해 시간이 굉장히 오래 걸린다.
비동기적으로 처리란 일을 병렬적으로 처리한다는 것이다.
function run(){
console.log("start");
setTimeout(()=>{
console.log("stop");
},3000);
console.log("running");
}
run();
알고리즘(비동기적 수행)
1. run()이 호출되면 'start'가 출력된다.
2. setTimeout함수는 node API에게 3초뒤에 콜백함수를 던져달라고 한다음 넘어간다.
3. 'running'이 출력된다.
4. Node API는 3초뒤에 콜백함수를 task queue에 전달한다. event loop는 call stack이 비워져있는것을 확인하고 그때 콜백함수는 call stack으로 들어와 출력된다.
자바스크립트는 비동기 처리를 위한 하나의 패턴으로 콜백 함수를 사용한다
비동기적으로 처리해야 하는 함수가 있을 때 그 함수는 콜백함수를 인수로 반드시 줘야 한다.
코드 📄
function run(){
try{
console.log("start!");
delaySeconds(()=>console.log("finished!"),2)
console.log("not finished");
}catch(error){
console.log(error.message);
}
}
function delaySeconds(callback,seconds){
if(!seconds|| seconds<0|| isNaN(seconds)){
throw new Error('시간 설정이 이상해');
}
return setTimeout(callback,seconds*1000);
}
run();
전통적인 콜백 패턴은 콜백 헬로 인해 가독성이 나쁘고 비동기 처리중 발생한 에러를 처리하기 곤란해 여러 비동기처리를 한번에 처리하기가 어렵다.
비동기적 처리를 위해 콜백 패턴을 사용하면 처리 순서를 보장하기 위해 여러개의 콜백 함수가 중첩되어 복잡해지는 것을 말한다.
loadScript('1.js', function(error, script) {
if (error) {
handleError(error);
} else {
// ...
loadScript('2.js', function(error, script) {
if (error) {
handleError(error);
} else {
// ...
loadScript('3.js', function(error, script) {
if (error) {
handleError(error);
} else {
}
});
}
})
}
});
코드 동작
1. 1.js를 로드한다. 그 후 에러가 없으면
2. 2.js를 로드한다. 그 후 에러가 없으면
3. 3.js를 로드한다. 그 후 또 다른 작업을 수행한다.
콜백 방식으로 비동기 처리를 했을 때 에러처리를 할 수 없다.
아래 코드를 보자
try {
setTimeout(() => { throw new Error('Error!'); }, 1000);
} catch (e) {
console.log('에러를 캐치하지 못한다..');
console.log(e);
}
해석
비동기처리 방식의 콜백함수는 timer함수같은 이벤트가 발생하면 task queue로 이동하고 호출 스택이 비워지면 호출 스택으로 이동되어 실행한다.
setTimeout은 비동기 처리 함수이므로 콜백함수가 실행되기까지 기다리지않고 실행했다 종료되어 호출스택에서 제거된다.
즉 콜백함수가 호출스택으로 이동할때는 이미 setTimeout은 호출스택에서 제거된 상태인데 이는 setTimeout의 콜백함수의 호출자가 setTimeout이 아니라는 뜻이다.
콜백함수에서 에러가 발생하면 예외처리는 콜백함수의 호출자 방향으로 전파되는데
setTimeout의 콜백함수의 호출자가 setTimeout이 아니므로 예외처리를 할 수가 없다.
이러한 문제들을 해결하기위해 Promise가 나오게 되었다.
promise는 비동기적으로 수행한 결과(성공 or 실패)를 알려주는 객체이다.
promise의 상태에는 pending, fulfilled, rejected가 있다.
제작 코드 📄
function delaySeconds(seconds){
return new Promise(function(resolve, reject) {
// 제작 코드
if(!seconds|| seconds<0|| isNaN(seconds)){
reject(new Error('시간 설정이 이상해'))
}
setTimeout(()=>resolve('완료!'),seconds*1000);
});
}
소비 코드 📄
delaySeconds(2)
// 소비 코드
.then(result => console.log(result))
.catch(console.log)
.finally(()=>console.log('finished!'));
순차적으로 처리해야 하는 비동기 작업이 여러 개 있을 때 프로미스 체이닝을 이용하면 해결할 수 있다.
코드 📄
function printNumber(ms){
return new Promise((resolve, reject)=>{
setTimeout(()=>resolve(1),ms);
})
}
printNumber(1000) // 1초 기다렸다가 promise 반환값 then에 전달
.then(result => result*2)
.then(result2 => result2*2)
.then(result3 =>result3*2)
.then(finalResult => console.log(finalResult));
promise chaining이 가능한 이유는 then, catch, finally를 호출하면 promise가 반환되기 때문이다.
function printNumber(ms){
return new Promise((resolve)=>{
setTimeout(()=>resolve(1),ms);
})
}
function multiply2(result,ms){
return new Promise((resolve)=>{
setTimeout(()=>resolve(result*2),ms);
})
}
printNumber(1000) // 1초 기다렸다가 promise 반환값 then에 전달
.then(result => multiply2(result,1000))
.then(result2 =>multiply2(result2,1000))
.then(result3 =>multiply2(result3,1000))
.then(finalResult => console.log(finalResult)); //8
then 핸들러가 promise를 반환하는 함수(multiply2)를 반환하고 다음 then 핸들러가 반환 값을 받기 때문에 promise chaining된다.
const kid = new Promise((resolve,reject)=>{
setTimeout(()=>resolve('👲'),1000);
})
const student = (kid)=>new Promise((resolve,reject)=>{
setTimeout(()=>reject(new Error(`${kid}=>👦`)),1000);
})
const adult= (student)=> new Promise((resolve,reject)=>{
setTimeout(()=>resolve(`${student}=>👨`),2000);
})
kid
.then(kid=>student(kid))
.catch(error=>{
return '👶';
})
.then(adult)
.then(console.log)
Promise 클래스에는 5개의 정적 메소드가 있다.
resolve(), reject() 메소드를 사용하면 Promise 객체를 생성할 필요 없이
즉각적으로 promise가 성공 ,실패했을 때 결과를 반환할 수 있다.
코드 📄
function getApple(){
return new Promise((resolve)=>{
setTimeout(()=>resolve('🍎'),3000)
})
}
function getBanana(){
return new Promise((resolve)=>{
setTimeout(()=>resolve('🍌'),2000)
})
}
function getStrawberry(){
return Promise.reject(new Error('딸기는 없습니다.'));
}
getApple()
.then(apple => getBanana()
.then(banana => console.log(apple + banana)));
// 코드 실행후 기본 promise가 5초뒤에 출력 🍎🍌
병렬적으로 한번에 모든 Promise들을 수행한다.
코드 📄
Promise.all([getApple(),getBanana()])
.then(result => console.log('all',result));
// 코드 실행후 all 메소드가 3초뒤에 출력 all [ '🍎', '🍌' ]
Promise.all은 요소 전체가 프라미스인 배열을 받는다.
코드 📄
Promise.all([getApple(),getBanana(),getStrawberry()])
.then(result => console.log('all-error',result)) // 실행 x
.catch(result => console.log('all-error',result));
// all-error Error: 딸기는 없습니다.
promise를 받아올 함수들중 에러가 발생하는 함수가 있을경우 catch를 써줘야한다.
그렇지 않으면 Promise.all이 반환하는 프라미스들은 에러와 함께 바로 거부된다.
주어진 promise중에서 제일 빨리 수행된것이 처리된다.
코드 📄
Promise.race([getApple(),getBanana()])
.then(result => console.log('race',result));
// 코드 실행후 race 메소드가 2초뒤에 출력 race 🍌
성공하든 실패하든 결과를 출력
코드 📄
Promise.allSettled([getApple(),getBanana(),getStrawberry()])
.then(result => console.log('settle',result));
/*
settle [
{ status: 'fulfilled', value: '🍎' },
{ status: 'fulfilled', value: '🍌' },
{
status: 'rejected',
reason: Error: 딸기는 없습니다.
at getStrawberry (C:\Users\82105\Desktop\khw\projects\javascript\practice.js:18:27)
at Object.<anonymous> (C:\Users\82105\Desktop\khw\projects\javascript\practice.js:35:44)
at Module._compile (node:internal/modules/cjs/loader:1358:14)
at Module._extensions..js (node:internal/modules/cjs/loader:1416:10)
at Module.load (node:internal/modules/cjs/loader:1208:32)
at Module._load (node:internal/modules/cjs/loader:1024:12)
at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:174:12)
at node:internal/main/run_main_module:28:49
}
]
*/