릴레이 블로깅을 달성하기 위해 오늘은 이전에 공부 햇던 프로미스를 한무 즐기기로 햇씁니다..
예전에 프로미스를 공부하다가 갑자기 실전에 마주하니까 머리가 하얘지는 경험을 했기 때문에, 오늘은 그 경험을 하지 않기 위해 프로미스를 다시 복습해봅니다.
● 프로미스 왜써야할까?
저는 어떤 것을 학습하기 이전에, 왜 필요하지에 대해 탐구하면 궁극적인 목적을 찾을 수 있어서 매번 '왜'에 대해 탐구하는 것은 습관을 들이면 좋은 것 같습니다.
미래에 어떤 값을 '꼭' 반환하겠다고 약속하는 비동기 함수
예를 들어, 만약 프로미스가 없이 비동기 함수로 값을 설정하고, 그 값을 가져오고 싶다면 어떻게 해야할까요?
let a = 1;
setTimeout(()=>{
a = 2;
}, 2000);
console.log(a)
위와 같이 a를 설정하고 2초뒤에 2를 수정하고 a를 출력해보았습니다.
// 1
하지만 1만 출력되고 있는 것을 볼 수 있습니다.
왜냐면 자바스크립트 엔진은 동기적으로 실행되기 때문입니다.
도대체 비동기, 동기가 무엇일까요?
동기는 순서대로, 비동기는 순서대로가 아니라 동시다발적으로
자바스크립트 엔진은 코드를 한줄씩 읽어갑니다. 하지만 모든 방식을 순서대로 처리하는 일은 그다지 효율적이지 못합니다. 하나의 일이 아주 오래 걸릴 수도 있는데, 그럼 뒤에 밀린 코드들은 하나의 코드가 실행이 완료되어야 다음 코드가 실행되기 때문입니다.
setTimeout(()=>{
console.log(4)
}, 2000);
console.log(1)
console.log(2)
console.log(3)
실제로 위의 코드는 1,2,3,4 순으로 출력됩니다. 하지만 setTimeout로 동기적인 함수였다면, 4,1,2,3 순으로 출력이 되었겠죠?
따라서 자바스크립트 엔진은 비동기함수들은 다른 곳에 저장해놓고, 동기적으로 실행되는 함수들만 먼저 순서대로 처리한 뒤 시간에 맞게 처리해야하는 비동기 함수들을 처리하게 됩니다.
비동기 함수는 setTimeout, setTimeInterval 제외하고 XMLHttpRequest(), fetch, eventListener, promise 등이 있습니다. 대부분은 서버에 어떤 요청을 보내는 함수들입니다.
그렇다면 아까 보았던 setTimout 예제에서 몇초의 시간이 흐른 뒤, 변수의 값을 수정하고 그 값을 출력하려면 어떻게 해야할까요?
let a = 1;
const callbackHell = (value, callback) => {
setTimeout(()=>{
a = value;
callback(a)
}, 2000)
}
callbackHell(2, console.log)
그냥 쉽게 생각해도, 콘솔로 값을 출력하려면 동기함수들을 모두 비동기 함수 안에 넣고 실행을 해야 차례대로 실행이 될 겁니다.
callbackHell이라는 함수로 값과 콜백함수를 받아오고, 비동기 함수인 setTimeout에서 값을 수정하고, 그 값을 출력하는 작업을 해주었습니다.
정리하자면 아래와 같습니다.
- 동기함수와 비동기 함수사이에 섞여 있다면 동기먼저 실행되고 비동기가 실행된다.
- 근데 비동기함수가 실행되고 나서 그 뒤에 있는 동기 함수를 실행하고 싶다면?
- 비동기 함수 내에서는 순서가 보장되니, 비동기 함수에 넣어준다.
참 쉽죠잉?
근데 차례대로 3, 4, 5 로 a값을 수정하고 또 그걸 차례대로 출력하고 싶다면?
let a = 1;
const callbackHell = (value, callback) => {
setTimeout(()=>{
a = value;
callback(a)
}, 2000)
}
callbackHell(2, (a)=> {
console.log(a);
callbackHell(3, (a)=>{
console.log(a);
callbackHell(4, (a)=>{
console.log(a);
callbackHell(5, console.log)
})
})
})
윽 이게 뭐죠? 코드가 보기 힘듭니다. 이런 것을 콜백 헬이라고 합니다.
콜백함수들의 지옥으로 보이죠? ㅎ_ㅎ 그래서 이런 setTimout, setTimeInterval만으로 처리를 하는 것은 가독성이 매우 떨어지게 됩니다.
● 써야하는 이유를 정리하자면
프로미스는 미래에 어떤 값을 반환하는 것을 이야기합니다. 비동기함수를 사용하는 이유는 자바스크립트 엔진은 동기적으로 실행되기 때문입니다. 만약 프로미스를 활용하지않고 비동기함수를 동기적으로 실행되는 것처럼 만들고 싶다면 콜백헬에 빠질 수 있습니다.
따라서 프로미스는 콜백헬에서 벗어나게 해주며, 미래에 어떠한 값을 가져와 반환할 수 있습니다.
+ 추가적으로 콜백함수를 통해 비동기함수에서 후속처리를 해주게 되면 호출자가 누구인지 모르기 때문에 (콜백함수니까) 오류를 캐치하기가 어렵다는 단점도 있습니다.
● 후속메서드 (then, catch, finally)
따라서 콜백함수가 아니라 프로미스에서는 then, catch, finally를 통해 후속처리를 해줍니다.
여기서 부터는 이전에 공부했던 프로미스 과제를 풀어 나가겟습니당
02_promiseConstructor.js
const getDataFromFilePromise = filePath => {
return new Promise((resolve, reject)=> {
fs.readFile(filePath, 'utf-8', (err, data)=>{
if (err) reject(err);
else resolve(data)
})
})
};
fs.readFile을 사용해서 filePath 인자가 들어오면, 서버에 요청을 하고 제대로 요청이 됐으면 resolve, 안되면 reject로 반환하도록 설정해주었습니다. 이때 세번째 인자에 콜백함수가 err, data가 들어오고, error가 있다면 reject로 반환되고 error가 없다면 resolve에 data로 넣어 반환해줍니다.
03_basicChaining.js
const user1Path = path.join(__dirname, 'files/user1.json');
const user2Path = path.join(__dirname, 'files/user2.json');
const readAllUsersChaining = () => {
return getDataFromFilePromise(user1Path)
.then(data1 => {
return getDataFromFilePromise(user2Path)
.then(data2 => {
return [JSON.parse(data1), JSON.parse(data2)]
})
})
04_promiseAll.js
const readAllUsers = () => {
// TODO: Promise.all을 이용해 작성합니다
return Promise.all([getDataFromFilePromise(user1Path), getDataFromFilePromise(user2Path)])
.then(data => data.map(item => JSON.parse(item)))
.then(data => {return data})
}
return 두번째 then 후속 메서드를 하고 코드를 끝내면 안되나? 하고 생각했는데, 생각해보니 후속메서드들은 모두 promise를 리턴하는군요.. 그래서 그 다음 return data를 해주고 있습니다. 근데 걍 다음 후속메서드 안쓰고 싶다면? 아래와 같이 해주면 댑니당
const readAllUsers = () => {
// TODO: Promise.all을 이용해 작성합니다
return Promise.all([getDataFromFilePromise(user1Path), getDataFromFilePromise(user2Path)])
.then(data => {
data = data.map(item => JSON.parse(item));
return data;
})
}
05_asyncAwait.js
const readAllUsersAsyncAwait = async () => {
// TODO: async/await 키워드를 이용해 작성합니다
const path1 = await getDataFromFilePromise(user1Path);
const path2 = await getDataFromFilePromise(user2Path);
return [JSON.parse(path1), JSON.parse(path2)]
}
흑흑 어웨잇 너무 편해잉