일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 |
- concurrency limit
- 프론트엔드
- compateto
- 딥다이브
- 모던 자바스크립트
- 스프링부트
- this
- 유효시간 설정 url
- NestJS
- bucket4j
- 검색
- invalid_grant
- oauth
- 우아한테크코스
- 코멘토 #코멘토실무PT #실무PT후기 #실무강의 #리액트강의 #웹프로그래밍 #react #웹개발실무
- Deep Dive
- TypeORM
- 우아한 테크코스
- 프론트엔드 과제
- 파일 url
- redis
- api 비동기처리
- Dev-Matching
- 자바스크립트
- AWS
- api 요청 수 제한
- 프리코스
- 타입스크립트
- 프로그래머스
- 음악 url 파일 다운로드
- Today
- Total
개발 알다가도 모르겠네요
Promisify를 간단하게 알아보자 본문
Promise 객체 생성
const p = new Promise((resolve, reject) => {
1. setTimeout(() => {resolve('success'); }, 2000);
2. setTimeout(() => {reject(new Error('fail')); },2000);
});
전통적인 형식의 비동기 실행 함수를 사용하는 코드를, Promise 기반의 코드로 변환하기 위해 Promise 객체를 직접 만드는 경우가 많습니다.
setTimeout 함수 예시
예를 들어 이런 wait이라는 함수가 있다고 합시다.
function wait(text, milliseconds) {
setTimeout(() => text, milliseconds);
}
wait 함수는 특정 밀리세컨즈만큼 시간이 지난 후에 text 파라미터로 전달받은 값을 리턴하는 함수입니다.
function wait(text, milliseconds) {
setTimeout(() => text, milliseconds);
}
fetch('https://jsonplaceholder.typicode.com/users')
.then((response) => response.text())
.then((result) => wait(`${result}`, 2000)) // 2초 후에 리스폰스의 내용 뒤에 'by Codeit' 추가하고 리턴
.then((result) => { console.log(result); });
코드를 실행해보면,
리스폰스의 내용과 by Codeit이 출력되지 않았습니다. 그 대신 undefined가 출력됐습니다.
function wait(text, milliseconds) {
setTimeout(() => text, milliseconds);
}
이 wait 함수는 내부에서 setTimeout 함수를 호출합니다. 그리고 setTimeout 함수의 첫 번째 파라미터로 들어간 콜백이 2초 후에 text를 리턴합니다. 그런데 여기서 혼동하면 안 되는 것은 wait 함수가
.then((result) => { return wait(`${result} by Codeit`, 2000); })
이 두 번째 then 메소드 안의 콜백에서 실행될 때,
wait 함수는 setTimeout 함수를 실행할 뿐 아무것도 리턴하지 않는다는 사실입니다.
setTimeout 함수 안의 콜백이 2초 후에 리턴하는 text는, wait 함수의 리턴값이 아닙니다.
wait 함수는 단지 setTimeout 함수를 실행하고 아무것도 리턴하지 않는 함수일 뿐입니다.
그리고 자바스크립트에서는 함수에서 아무것도 리턴하지 않으면 undefined를 리턴하는 것으로 간주하기 때문에 wait 함수의 리턴값은 undefined입니다.
따라서 세 번째 then 메소드의 콜백으로 undefined가 넘어가고, 그래서 위 이미지에서 보이는 것처럼 undefined가 출력된 겁니다.
setTimeout은 비동기 실행되는 함수인데요.
Promise Chaining 안에서 이렇게 비동기 실행되는 함수를 바로 사용하면, 나중에 실행되는 부분의 리턴값(여기서는 text)를 Promise Chain에서 사용할 수 없게 됩니다.
따라서
function wait(text, milliseconds) {
const p = new Promise((resolve, reject) => {
setTimeout(() => { resolve(text); }, 2000);
});
return p;
}
지금 wait 함수 안에서 Promise 객체를 직접 생성했고, executor 함수 안에서 setTimeout 함수를 호출했습니다.
그리고 setTimeout 함수 안의 콜백에서 resolve 함수를 호출하는데 이 때 그 아규먼트로 text를 넣었습니다.
그렇다면 Promise 객체 p는 2초 후에 fulfilled 상태가 될 것이고, 그 작업 성공 결과는 파라미터 text의 값이 될 될 것입니다.
wait 함수는 이제 Promise 객체 p를 리턴합니다.
function wait(text, milliseconds) {
const p = new Promise((resolve, reject) => {
setTimeout(() => { resolve(text); }, 2000);
});
return p;
}
fetch('https://jsonplaceholder.typicode.com/users')
.then((response) => response.text())
.then((result) => wait(`${result}`, 2000)) // 2초 후에 리스폰스의 내용 뒤에 'by Codeit' 추가하고 리턴
.then((result) => { console.log(result); });
이렇게 전통적인 형식의 비동기 실행 함수를 Promise 객체로 감싸서 그 Promise 객체를 리턴하는 형식으로 만드는 작업을 Promisify 라고 합니다.
콜백 헬(callback hell)과 Promise
Node.js에는 다음과 같이 특정 파일의 내용을 읽기 위해 사용되는 readFile이라는 비동기 실행 메소드가 있습니다.
fs.readFile('file1.txt', 'utf8', (error, data) => {
if (err) {
console.log(err);
} else {
console.log(data);
}
});
예를 들어, 위 코드에서 이제 file1.txt 파일의 내용을 출력하고 나서 그 다음 파일의 내용을 또 출력해야한다고 가정하면
fs.readFile('file1.txt', 'utf8', (error1, data1) => {
if (error1) {
console.log(error1);
} else {
console.log(data1);
fs.readFile('file2.txt', 'utf8', (error2, data2) => {
if (error2) {
console.log(error2);
} else {
console.log(data2);
fs.readFile('file3.txt', 'utf8', (error3, data3) => {
if (error3) {
console.log(error3);
} else {
console.log(data3);
}
});
}
});
}
});
이런식으로 바로 순차적으로 비동기 실행 함수들을 실행하려고 하면 콜백 안에 또 콜백이 있고, 그 안에 또 콜백이 있는
콜백 헬(콜백 지옥, callback hell) 현상을 초래하게 됩니다.
function readFile_promisified(filename) {
const p = new Promise((resolve, reject) => {
fs.readFile(filename, 'utf8', (error, data) => {
if (error) {
reject(error); // 에러 발생 시 -> rejected
} else {
resolve(data); // 파일 내용 읽기 완료 -> fulfilled
}
});
});
return p;
}
readFile_promisified('file1.txt')
.then((data) => { console.log(data); return readFile_promisified('file2.txt'); })
.then((data) => { console.log(data); return readFile_promisified('file3.txt'); })
.then((data) => { console.log(data); })
.catch((error) => { console.log(error); });
readFile_promisified 함수는 Promise 객체를 리턴하기 때문에 자유롭게 Promise Chain 안에서 사용할 수 있습니다.
이렇게 원하는 경우에는 전통적인 형식의 비동기 실행 함수를 Promisify해서 콜백 헬을 방지하고, 가독성 높은 코드를 작성할 수 있습니다.
Promisify를 하면 안 되는 함수
전통적인 형식의 비동기 실행 함수라고 해서 모두 Promisify해서 사용해도 되는 것은 아닙니다.
기존의 비동기 실행 함수들 중에서도 그 콜백을 한번만 실행하는 것들(setTimeout, readFile 등)만 Promisify해서 사용해도 되지만,
만약 콜백을 여러 번 실행하는 함수들(setInterval, addEventListener 등)인 경우에는 이렇게 Promisify하면 안 됩니다.
왜냐하면 Promise 객체는 한번 pending 상태에서 fulfilled 또는 rejected 상태가 되고나면 그 뒤로는 그 상태와 결과가 바뀌지 않기 때문입니다.
const box = document.getElementById('test');
let count = 0;
function addEventListener_promisified(obj, eventName) { // 이런 Promisify는 하지 마세요
const p = new Promise((resolve, reject) => {
obj.addEventListener(eventName, () => { // addEventListener 메소드
count += 1;
resolve(count);
});
});
return p;
}
addEventListener_promisified(box, 'click').then((eventCount) => { console.log(eventCount); });
특정 이벤트가 발생할 때마다 count라고 하는 변수의 값을 1씩 늘려서 resolve 함수의 파라미터로 전달해서 실행하도록 하는 내용이 들어있습니다.
하지만 이 코드를 실행하고 box를 클릭해보면 처음에 1이 딱 출력되고 나서 그 다음 count 값들은 출력되지 않습니다.
왜냐하면 pending 상태에 있던 Promise 객체(여기서는 p 객체)가 한번 fulfilled 상태 또는 rejected 상태가 되고 나면 Promise 객체의 상태 및 결과가 고정되어 그 뒤로는 바뀌지 않기 때문입니다.
따라서 지금 위 코드에 보이는 resolve(count)라고 하는 코드가 box 버튼을 클릭할 때마다 여러 번 실행된다고 해도 p 객체가 갖고 있는 상태와 결과는 변하지 않습니다.
이렇게 콜백이 여러 번 실행되어야하는 비동기 실행 함수인 경우에는 Promisify를 하면 안 됩니다.