자바스크립트의 비동기 처리방식의 문제점을 해결하기 위한 방법으로 Promise
가 있다.
the
promise
object represents the eventual completion (or failure) of an asynchronous operation, and its resulting value.
Promise 객체는 비동기 작업의 미래의 완료 또는 실패와 그 결과 값을 나타낸다.
// arrow function 쓴 경우
const myPromise = new Promise((resolve, reject) => {
//구현
})
const myPromise = new Promise(function(resolve, reject) {
//구현
})
new Promise
로 생성된 인스턴스 객체는 '객체'이기 때문에 변수로 할당하거나 함수의 인자로 사용할 수 있다.
executor의 인수 resolve
와 reject
는 자바스크립트가 자체적으로 제공하는 콜백이다.
resolve(value)
--> 일이 성공적으로 끝난 경우, 그 결과를 나타내는 value와 함께 호출reject(error)
--> 에러 발생 시 에러 객체를 나타내는 error와 함께 호출function getData(callbackFunc) {
$.get('url 주소/products/1', function(response) {
callbackFunc(response);
});
}
getData(function(tableData) {
console.log(tableData);
callback 함수를 활용하여 비동기처리를 한 위의 코드에서 Promise를 적용한다면, 아래와 같이 표현할 수 있다.
function getData(callback) {
// new Promise() 추가
return new Promise(function(resolve, reject) {
$.get('url 주소/products/1', function(response) {
// data를 받는데 성공하면 resolve() 호출
resolve(response);
});
});
}
//getData()의 실행이 끝나면 호출되는 then()
getData().then(function(tableData) {
//resolve()의 결과 값이 여기로 전달됨
console.log(tableData); //$.get()의 response 값이 tableData에 전달됨
});
Promise
는 이들 중 한가지의 상태를 갖는다.
then
과 catch
를 사용한다.
function getData(callback) {
// new Promise() 추가
return new Promise(function(resolve, reject) {
$.get('url 주소/products/1', function(response) {
reject("데이터를 받아오는 데에 실패했습니다.");
}); // promise가 reject되었을때, error을 나타내줌
});
}
//getData()의 실행이 끝나면 호출되는 then()
getData()
.then()
.catch(e => console.log("error: ", e))
});
여기서 중요한 점은 then
메소드는 다시 Promise
를 반환한다는 것이다.
다시 Promise 객체를 반환하게 되면 then
,catch
메소드를 사용할 수 있게 되며, 이를 통해 연속적으로 then
을 사용하여 Promise Chaining이 가능하게 된다.
콜백에서 나타났던 콜백 지옥처럼, Promise에서도 여러개의 비동기 작업을 나타내는 Promise Chaining이 나타난다.
new Promise(function(resolve, reject) {
setTimeout(() => resolve(1), 1000); // (*)
}).then(function(result) { // (**)
alert(result); // 1
return result * 2;
}).then(function(result) { // (***)
alert(result); // 2
return result * 2;
}).then(function(result) {
alert(result); // 4
return result * 2;
});
.then
핸들러가 호출실제 코딩을 할 때는 Promise 객체를 직접 생성해서 리턴해주는 코드 보다는 어떤 라이브러리의 함수를 호출해서 리턴받은 Promise 객체를 사용하는 경우가 더 많다.
대표적으로 REST API를 호출할 때 사용되는 브라우저 내장 함수인 fetch()
가 있다. fetch()
함수는 API의 URL을 인자로 받고, 미래 시점에 얻게될 API 호출 결과를 Promise 객체로 리턴한다.
fetch("url주소/products/1")
.then((response) => console.log("response:", response))
.catch((error) => console.log("error: ", error))
fetch("/article/promise-chaining/user.json")
.then(response => response.json())
.then(user => fetch(`https://api.github.com/users/${user.name}`))
.then(response => response.json())
.then(githubUser => new Promise(function(resolve, reject) { //(*)
let img = document.createElement('img');
img.src = githubUser.avatar_url;
img.className = "promise-avatar-example";
document.body.append(img);
setTimeout(() => {
img.remove();
resolve(githubUser); //(**)
}, 3000);
}));
.then(githubUser => alert(`Finished showing ${githubUser.name}`));
(*)
로 표시한 곳의 .then
핸들러는 이제 setTimeout
안의 resolve(githubUser)
를 호출했을 때((**)) 만 처리상태가 되는 new Promise를 반환한다. 체인의 다음 .then
은 이를 기다린다.
비동기 동작은 항상 프라미스를 반환하도록 하는 것이 좋다. 지금은 체인을 확장할 계획이 없더라도 이렇게 구현해 놓으면 나중에 체인 확장이 필요한 경우 손쉽게 체인을 확장할 수 있다.
코드를 재사용 가능한 함수 단위로 분리한다면 다음과 같다.
function loadJson(url) {
return fetch(url)
.then(response => response.json());
}
function loadGithubUser(name) {
return fetch(`https://api.github.com/users/${name}`)
.then(response => response.json());
}
function showAvatar(githubUser) {
return new Promise(function(resolve, reject) {
let img = document.createElement('img');
img src = githubUser.avatar_url;
img.className = "promise-avatar-example";
document.body.append(img);
setTimeout(() => {
img.remove();
resolve(githubUser);
}, 3000);
});
}
loadJson('/article/promise-chaining/user.json')
.then(user => loadGithubUser(user.name))
.then(showAvatar)
.then(githubUser => alert(`Finished showing ${githubUser.name}`));