콜백함수는 다른 코드(함수 또는 메서드)에게 인자로 넘겨지므로써 그 제어권도 함께 위임한 함수.
콜백함수를 위임받은 코드는 자체적인 내부 로직에 의해 콜백함수를 적절한 시점에 실행한다.
bind(thisArg)
함수를 사용해야한다setTimeout(obj1.func.bind(obj1), 1000)
동기적인 코드 - 현재 실행 중인 코드가 완료된 후에야 다음 코드를 실행하는 방법
비동기적인 코드 - 현재 실행 중인 코드의 완료 여부와 무관하게 즉시 다음 코드로 넘어가는 것
비동기 제어(처리)란 비동기 코드 완료 이후에 실행되어야할 코드들을 지정하는 것.
webApi를 이용한 비동기 처리
콜백 함수 이용 (콜백 기반 비동기 프로그래밍)
비동기 프로그래밍의 일반적인 접근법이다.
무언가를 비동기적으로 수행하는 함수는 함수 내 동작이 모두 처리되 후 실행되어야 하는 함수가 들어갈 콜백을 인수로 반드시 제공해야한다.
콜백 지옥
비동기 동작을 하는 함수를 중첩적으로 호출하면서 화살표 함수 모양의 콜백 안에 콜백을 넣는 것.
연속으로 실행할 비동기 코드가 많을 경우 콜백함수를 이용 시 콜백 지옥이 발생할 수 있다.
콜백지옥이 일어나는 이유는 비동기 코드 실행과 처리를 연속적으로 해야하기 때문이다.
콜백 지옥 해결 방법
시간이 걸리는 비동기 동작을 실행함.
executor라는 콜백함수 안에 작성되어 프로미스 객체 생성 시 인자로 넘겨진다.
resolve, reject
라는 두 개의 콜백을 인자로 받는데 둘 중 하나를 반드시 호출해야한다. resolve(value)
- 앞 코드가 성공적으로 완료되었을 경우 그 결과를 인자로 하여 호출reject(error)
- 앞 코드 실행 중 에러 발생 시 에러객체를 인자로 하여 호출resolve
나 reject
를 호출한다state
처음엔 pending(보류)
였다가 resolve호출 시 fulfilled
, reject호출 시 rejected
로 변경
result
처음엔 undefined
였다가 resolve호출 시 value
로, reject호출 시 error
로 변경됨
프로미스 객체가 처음 생성된 후에 ****executor의 실행 결과에 따라 상태와 결과가 변하는 것
executor는 보통 시간이 걸리는 일을 수행한다.
일이 끝나면 resolve나 reject함수를 호출하는데, 이때 프로미스 객체의 상태가 변화한다.
이행(resolve) 혹은 거부(reject) 상태의 프로미스는처리된 프로미스
라고 부르고,
반대의 프로미스는대기상태(pending)의 프로미스
라고 한다.
한 번 변경된 상태는 더 이상 변하지 않는다.
처리가 끝난 프로미스에 resolve와 refect를 호출하면 무시된다.
executor함수 내부의 코드에 꼭 비동기 로직만 들어갈 수 있는 건 아니다.
resolve와 reject를 즉시 호출할 수도 있다. (프로미스 객체는 즉시 이행 상태가 됨)
.then/catch/finally
핸들러는 프라미스가 처리되길 기다리지만 let promise = new Promise(resolve => resolve("완료!"));
promise.then(alert); // 완료! (바로 출력됨) 하지만 밑의 다른 코드 이후에..
then
첫번째 인자
두번쨰 인자
성공 경우만 다루고 싶다면 첫번째인수만, 실패 경우만 다루고 싶다면 첫번째 인수를 null로 하고 2번째 인수만 넣어주면 됨
// 성공 경우
let promise = new Promise(function(resolve, reject) {
setTimeout(() => resolve("done!"), 1000);
});
// resolve 함수는 .then의 첫 번째 함수(인수)를 실행합니다.
promise.then(
result => alert(result), // 1초 후 "done!"을 출력
error => alert(error) // 실행되지 않음
);
// 실패 경우
let promise = new Promise(function(resolve, reject) {
setTimeout(() => reject(new Error("에러 발생!")), 1000);
});
// reject 함수는 .then의 두 번째 함수를 실행합니다.
promise.then(
result => alert(result), // 실행되지 않음
error => alert(error) // 1초 후 "Error: 에러 발생!"를 출력
);
catch
.then(null,f)
과 같다.let promise = new Promise((resolve, reject) => {
setTimeout(() => reject(new Error("에러 발생!")), 1000);
});
// .catch(f)는 promise.then(null, f)과 동일하게 작동합니다
promise.catch(alert); // 1초 뒤 "Error: 에러 발생!" 출력
finally
프로미스 객체의 (소비코드를 처리하는) .then
, .catch
, .finally
메소드를 체이닝 형태로 이어서 사용하는 것.
.then
, .catch
, .finally
의 핸들러에서 반환을 하게되면 프라미스를 반환한다.
나머지 체인은 반환된 프라미스가 처리될 때까지 대기한다.
처리가 완료되면 프라미스의 result
(값 또는 에러)가 다음 체인으로 전달된다.
값 반환하기
어떤 값을 반환하면 그 값을 성공 결과로 가진 (이행된)promise객체가 반환된다.
이후의 핸들러는 결과 값을 즉시 전달 받아 실행되게 된다.
let promise = new Promise(function(resolve, reject) {
setTimeout(() => resolve(1), 1000);
});
promise.then(function(result) {
alert(result); // 1
return result * 2;
});
promise.then(function(result) {
alert(result); // 1
return result * 2;
});
promise.then(function(result) {
alert(result); // 1
return result * 2;
});
promise반환하기
promise를 반환하거나 생성과 동시에 반환할 수도있다.
이 경우 이어지는 핸들러는 프라미스가 처리될 때까지 기다리다가 처리가 완료되면 그 결과를 받는다.
new Promise(function(resolve, reject) {
setTimeout(() => resolve(1), 1000);
}).then(function(result) {
alert(result); // 1
return new Promise((resolve, reject) => { // (*)
setTimeout(() => resolve(result * 2), 1000);
});
}).then(function(result) {
alert(result); // 2초 후에 2가 찍힌다.
})
fetch의 경우
응답을 받아오는 비동기로직 + 응답 값을 다운받아 변환하는 비동기 로직
fetch('/article/promise-chaining/user.json')
.then(response => response.json())
.then(user => alert(user.name));
// 서버가 응답하면 응답값을 받아온다.
// 하지만 사용할 body데이터는 전부 다운로드되지 않은 상태다.
// json()함수로 데이터를 다운로드 받고, 받은 데이터를 js객체로 변환시킨다.
// 변환된 js객체를 결과값으로 하는 이행된 프로미스를 반환한다.
프라미스가 거부되면 제어 흐름이 제일 가까운 rejection핸들러로 넘어가기 때문에 프라미스 체인을 사용하면
에러를 쉽게 처리할 수 있다.
.catch
는 첫번째 핸들러일 필요가 없고 하나 혹은 여러 개의 .then
뒤에 올 수 있다.
위의 어떤 핸들러에서 오류가 발생하든 가장 가까운 catch에서 잡는다.
executor함수 내에서의 암시적 try...catch
new Promise((resolve, reject) => {
throw new Error("에러 발생!");
}).catch(alert); // Error: 에러 발생!
핸들러 내에서의 암시적 try...catch
.then
핸들러 안에서 throw
를 사용해 에러를 던지면, 거부된 프라미스를 반환하게 되어 제어 흐름이 가장 가까운 에러 핸들러로 넘어간다.throw
문이 만든 에러뿐만 아니라 핸들러 위쪽에서 발생한 비정상 에러 또한 잡는다.new Promise((resolve, reject) => {
resolve("ok");
}).then((result) => {
throw new Error("에러 발생!"); // 프라미스가 거부됨
}).catch(alert); // Error: 에러 발생!
new Promise((resolve, reject) => {
resolve("ok");
}).then((result) => {
blabla(); // 존재하지 않는 함수
}).catch(alert); // ReferenceError: blabla is not defined
.catch
핸들러 안에서 에러가 성공적으로 처리되면 가장 가까운 곳에 있는 .then
throw
를 던지면 제어 흐름이 가장 가까운 곳에 있는 에러 핸들러로unhandledrejection
.then/catch/finally
의 핸들러는 비동기적으로 실행된다.
비동기로 작동하는 코드가 없고 즉시 resolve가 이행되더라도 나머지 코드를 먼저 실행시킨다.
let promise = Promise.resolve(); // 이행 상태의 프로미스 객체를 즉시 만드는 방법
promise.then(() => alert("프라미스 성공!"));
alert("코드 종료"); // 이 얼럿 창이 가장 먼저 나타납니다.
마이크로태스크 큐란?
비동기 작업을 처리를 위한 js런타임 환경 안에 있는 큐
.then/catch/finally
핸들러가 큐에 들어간다.unhandledrejection
이벤트를 트리거 한다.프라미스를 좀 더 편하게 사용할 수 있는 문법적 설탕.
읽고, 쓰기 쉬운 비동기 코드를 작성할 수 있다.
async함수
async
를 붙이면 해당 함수는 항상 프라미스를 반환한다.await
를 사용할 수 있다.async function f() {
return 1;
}
f().then(alert); // 1
await
await
키워드를 만나면 async 함수 내부 코드 실행을 중지하고,에러 핸들링
프라미스가 정상적으로 이행되면 await promise
는 프라미스 객체의 result
에 저장된 값을 반환하지만
프라미스가 거부되면 throw
문을 작성한 것처럼 에러가 던져진다.
try...catch문을 이용해 에러를 핸들링 할 수 있다.
async function f() {
try {
let response = await fetch('http://유효하지-않은-url'); //처리가 완료되면 result를 반환한다
let user = await response.json();
} catch(err) {
// fetch와 response.json에서 발행한 에러 모두를 여기서 잡는다.
alert(err);
}
}
f();