March 22, 2021
자바스크립트에서 비동기 처리하는 방법
Example
function requestData(callback) {
setTimeout(() => {
callback({ name: 'abc', age: 23 });
}, 1000);
}
function onSuccess(data) {
console.log(data);
}
console.log('call requestData');
requestData(onSuccess);requestData 함수는 callback 함수를 인자로 받고 비동기 처리가 끝나면 인자로 받은 함수를 호출한다.동작 순서
call requestData 출력{ name: 'abc', age: 23 } 출력콜백 패턴은 콜백이 조금만 중첩돼도 코드가 상당히 복잡해진다 → 콜백 지옥
function requestData1(callback) {
// ...
callback(data);
}
function requestData2(callback) {
// ...
callback(data);
}
function onSuccess1(data) {
console.log(data);
requestData2(onSuccess2);
}
function onSuccess2(data) {
console.log(data);
// ...
}
requestData1(onSuccess1);동작 순서
requestData1(onSuccess1) : onSuccess1 콜백 함수를 매개변수로 넘겨 비동기 함수 호출onSuccess1(data) 함수 호출console.log 실행requestData2(onSuccess2) 비동기 함수 호출onSuccess2(data) 함수 호출console.log 실행Promise 객체는 세 가지의 상태를 가질 수 있다.
Example
requestData1()
.then(data => {
console.log(data);
return requestData2();
})
.then(data => {
console.log(data);
});동작 순서
requestData1() 비동기 함수 호출data를 받아서 console.log 실행하고 두 번째 함수(requestData2) 호출console.log 실행한다.new 키워드
const p1 = new Promise((resolve, reject) => {});reject 함수 호출
const p2 = Promise.reject('error message');reject 함수를 호출하는 방식으로 Promise 객체를 생성할 경우, 실패 상태인 Promise 객체가 만들어진다.resolve 함수 호출
const p3 = Promise.resolve(param);resolve 함수를 호출하는 방식으로 Promise 객체를 생성할 경우, 성공 상태인 Promise 객체가 만들어진다.then 메서드 : 비동기 처리가 끝난 다음에 처리할 일을 정의할 수 있다.Example1
requestData().then(onResolve, onReject);onResolve 함수가 호출된다.onReject 함수가 호출된다.Example2
Promise.resolve(123).then(data => console.log(data));Example3
Promise.reject('error').then(null, data => console.log(data));then 메서드는 항상 Promise 객체를 반환하기 때문에 체인 형태로 연결할 수 있다.Example1
function requestData1() {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(10);
}, 1000);
});
}
function requestData2() {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(20);
}, 1000);
});
}
requestData1()
.then(data => {
console.log(data); // 10
return requestData2();
})
.then(data => {
console.log(data); // 20
return data + 1;
})
.then(data => {
console.log(data); // 21
throw new Error('some error');
})
.then(null, error => {
console.log('error!!!'); // error!!!
})
.then(data => {
console.log(data); // undefined
});requestData1은 Promise 객체를 반환하는 함수이고 비동기 처리가 끝나면 data를 return 받아서 필요한 처리를 한다. 그리고 두 번째 함수(requestData2)를 호출하고 있다.requestData2 함수도 Promise 객체를 반환한다.then의 data는 requestData2 함수에서 반환한 값이다. 이 값을 받아서 필요한 처리를 하고 data + 1을 데이터로 하는 Promise 객체를 반환한다.then의 data는 위에서 반환한 data + 1 의 값이 된다. 이 값을 받아서 필요한 처리를 하고 예외를 발생시키고 있다. 예외를 발생시키면 then 메서드는 new Error('some error') 를 데이터로 하는 Promise 객체를 반환하고 실패 상태가 된다.then 메서드의 두 번째 매개변수로 들어가 있는 함수가 호출된다. 여기서는 아무런 값도 반환하고 있지 않기 때문에 undefined를 반환한다. 즉, undefined를 데이터로 하는 Promise 객체를 반환한다. 이 때의 상태는 성공 상태이다.then 메서드에서 data는 undefined가 되고 이 undefined를 받아서 처리를 하게 된다.Example2
Promise.reject('err')
.then(() => console.log('then 1'))
.then(() => console.log('then 2'))
.then(
() => console.log('then 3'),
() => console.log('then 4'),
)
.then(
() => console.log('then 5'),
() => console.log('then 6'),
);then 메서드에 두 번째 매개변수 함수를 정의하지 않았을 때는 then 메서드는 그 Promise 객체를 그대로 반환한다.then 메서드는 각각 Promise.reject('err') 에서 만들어진 Promise 객체가 그대로 반환된다.then에서 두 번째 매개변수 함수가 호출되어 then 4 가 출력된다. 출력 후에는 아무런 값도 반환하지 않기 때문에 undefined를 데이터로 하고 성공 상태인 Promise 객체를 반환한다.then에서 첫 번째 매개변수 함수가 호출되어 then 5 가 출력된다.catch 메서드는 실패 상태인 Promise 객체를 처리하기 위해 사용된다.Example1
Promise.reject(1).then(null, error => {
console.log(error);
});
Promise.reject(1).catch(error => {
console.log(error);
});catch 메서드는 then 메서드를 이용해서 두 번째 함수를 입력하는 것과 같은 역할을 한다.Example2
Promise.resolve()
.then(() => {
throw new Error('some error');
})
.catch(error => {
console.log(error);
});then 메서드를 이용하는 것 보다는 catch 메서드를 사용하는 것이 가독성에 더 좋다.Promise.resolve() 에서 예외가 발생했거나 then 메서드 내 함수에서 예외가 발생한 것들 모두 catch에서 예외처리 할 수 있다.Example3
Promise.reject(10)
.then(data => {
console.log('then1:', data);
return 20;
})
.catch(data => {
console.log('catch:', data);
return 30;
})
.then(data => {
console.log('then2:', data);
});catch 메서드도 then 메서드처럼 Promise 객체를 반환한다.catch 이후에도 then을 계속해서 사용할 수 있다.동작 순서
then 메서드 내의 첫 번째 매개변수인 함수는 생략된다.catch 메서드에서 10이라는 데이터를 받아서 catch: 10 이 출력되고 30 이라는 데이터를 가진 Promise 객체를 반환한다. 이 때 상태는 성공 상태이다.then 메서드에서 data 로 30 을 받아서 then2: 30 을 출력한다.finally 메서드로 성공 상태와 실패 상태 모두를 처리할 수 있다.성공 상태와 실패 상태 모두를 처리할 수 있기 때문에 then 메서드에 똑같은 함수 두 개를 매개변수로 넘기는 것과 비슷하다고 보면 된다. 아래의 두 코드는 비슷한 역할을 한다.
.finally(() => {
console.log('onFinally');
}).then(() => {
console.log('onFinally');
}, () => {
console.log('onFinally');
})두 코드의 차이점
finally 에는 데이터가 넘어오지 않는다.finally는 이전에 있던 Promise 객체를 그대로 반환한다.Example1
Promise.resolve(10)
.then(data => {
console.log('onThen', data);
return data + 1;
})
.catch(error => {
console.log('onCatch');
return 100;
})
.finally(() => {
console.log('onFinally');
})
.then(data => {
console.log('onThen', data);
return data + 1;
});then : data로 10을 받아오고 onThen 10 출력하고 나서 11을 데이터로 가진 성공 상태의 Promise 객체를 반환한다.catch 부분은 건너 뛴다.onFinally 가 출력된다. 이 때, finally 메서드는 11을 데이터로 가진 성공 상태의 Promise 객체를 그대로 반환한다.then 메서드에서 data로 11을 받아오고 onThen 11이 출력된다.Example2
Promise.reject(10)
.then(data => {
console.log('onThen', data);
return data + 1;
})
.catch(error => {
console.log('onCatch');
return 100;
})
.finally(() => {
console.log('onFinally');
})
.then(data => {
console.log('onThen', data);
return data + 1;
});then은 건너 뛴다.onCatch 가 출력되고 100을 데이터로 가지는 성공 상태의 Promise 객체가 반환된다.onFinally 가 출력되고 100을 데이터로 가지는 성공 상태의 Promise 객체가 반환된다.then 메서드에서 data로 100을 받아오고 onThen 100이 출력된다.Example3
function requestData() {
return fetch()
.catch(error => {
// ...
})
.finally(() => {
sendLogToServer('requestData finished');
});
}
requestData().then(data => console.log(data));catch 메서드 호출시 넘기는 함수 부분이 실행되고 그 이후에 finally가 처리된다.finally는 이전에 생성된 Promise 객체를 그대로 반환하기 때문에 requestData 함수가 반환하는 값은 finally의 처리와는 상관이 없다.Example
Promise.all([requestData1(), requestData2()]).then(([data1, data2]) => {
console.log(data1, data2);
});Example
Promise.race([
requestData(),
new Promise((_, reject) => setTimeout(reject, 3000)),
])
.then(data => console.log('성공', data))
.catch(error => console.log('실패'))requestData 함수 실행이 3초 안에 끝나지 않으면 Promise.race 함수가 반환하는 Promise 객체는 실패 상태가 되는 것이다.requestData 비동기 처리가 3초 안에 끝난다면 성공 상태의 Promise 객체를 반환할 것이다.then 메서드보다 가독성이 좋다.Example
async function getData() {
return 123;
}
getData().then(data => console.log(data));async 키워드를 이용해서 정의를 하면 이 함수는 async-await 함수가 된다.then 메서드를 사용할 수 있다.Example2
async function getData() {
return Promise.resolve(123); // 성공 상태인 Promise 객체 반환
}
getData()
.then(data => console.log('성공', data))
.catch(data => console.log('실패', data))getData 함수에서 성공 상태인 Promise 객체를 반환하고 있기 때문에 성공 123 이 출력된다.function requestData(value) {
return new Promise(resolve =>
setTimeout(() => {
console.log('requestData:', value);
resolve(value);
}, 1000);
);
}
async function printData() {
const data1 = await requestData(10);
const data2 = await requestData(20);
console.log(data1, data2);
}
printData();
/*
requestData: 10
requestData: 20
10 20
*/await 키워드를 입력하면 이 Promise 객체가 성공 상태나 실패 상태가 될 때까지 기다린다.await 키워드로 비동기 처리를 기다리면서 동기 프로그래밍 방식으로 코드를 작성할 수 있다. async function getData() {
console.log('getData 1');
await Promise.reject();
console.log('getData 2');
await Promise.resolve();
console.log('getData 3');
}
getData()
.then(() => console.log('성공'))
.catch(error => console.log('실패'))
/*
getData 1
실패
*/await 키워드 오른쪽에 있는 Promise 객체가 실패 상태가 되면 async-await 함수는 그 Promise의 상태와 데이터를 그대로 반환한다.getData 실행 중 await Promise.reject(); 를 만나서 catch 부분에 있는 실패 로그가 출력되고 getData 함수에서 이후의 코드는 실행하지 않는다.function getDataPromise() {
asyncFunc1()
.then(data => {
console.log(data);
return asyncFunc2();
})
.then(data => {
console.log(data);
});
}
async function getDataAsync() {
const data1 = await asyncFunc1();
console.log(data1);
const data2 = await asyncFunc2();
console.log(data2);
}then 메서드를 사용할 필요가 없기 때문에 좀 더 간결하다.async function getData() {
const [data1, data2] = await Promise.all([asyncFunc1(), asyncFunc2()]);
}async function getData() {
try {
await doAsync(); // 비동기 함수 호출
return doSync(); // 동기 함수 호출
} catch (error) {
console.log(error);
return Promise.reject(error);
}
}catch 문에서 처리된다.getData 함수가 async-await 함수가 아니었다면 doAsync 함수에서 발생하는 예외는 catch 문에서 처리되지 않는다. doAsync 함수의 처리가 끝나는 시점을 알 수 없기 때문.