2022. 7. 27. 10:05ㆍWEB/Javascript
# callback
const printString = (string) =>{ setTimeout( ()=>{ console.log(string) }, Math.floor(Math,random() * 100) + 1 ) } const printAll = () =>{ printString("A"); printString("B"); printString("C"); }
- 위 코드의 결과는 실행시마다 달라진다. 비동기 함수이지만 순서를 제어할수가 없다.
const printString = (string, callback) =>{ setTimeout( ()=>{ console.log(string); callback(); console.log(`${string}종료`) }, Math.floor(Math.random() * 1000) + 1 ) } const printAll = () =>{ printString("A", ()=>{ printString("B", () =>{ printString("C",()=>{}) }); }); };
- 위 코드는 콜백함수를 이용하여 순서를 제어한다. 좋은 방법일것 같지만 코드가 물결치듯이 가독성이 좋지 않다. 이것을 'callback hell' 이라고도 불른다.
- callback을 통한 비동기처리방식은 에러처리에 한계가 있다.
- callback함수는 이벤트가 발생하면 'Task queue'로 이동한 뒤 호출 스택이 비어졌을때 호출스택으로 이동되어 실행된다.
- setTimeout함수는 비동기 함수이므로 콜백함수 실행완료전 종료되어 호출스택에서 제거된다.
- 이후 setTimeout의 콜백함수는 호출스택이 비어졌을 때 이동되어 실행된다.
- 이상황에서는 setTimeout함수는 제거된상태이므로 setTimeout함수의 콜백함수를 호출한 것은 논리적으로 setTimeout함수가 아니라고 봐야된다.
- 따라서 setTimeout 함수의 콜백 함수 내에서 발생시킨 에러는 catch 블록에서 캐치되지 않아 프로세스는 종료된다.try { setTimeout(() => { throw new Error('Error!'); }, 1000); } catch (e) { console.log('에러를 캐치하지 못한다..'); console.log(e); }
# Callback error handling Design
- try catch로 안되는 에러처리를 위한 디자인패턴const somethingGonnaHappen = callback =>{ waitingUntillSomethingHappens() //4, 8번째의 결과를 반환 if(isSomethingGood){ callback(null, something); } if(isSomethingBad){ callback(something, null); } } somethingGonnaHappen((err,data) => { if(err){ console.log("ERR!!"); return; } return data; })
# Promise
- Promise는 비동기 처리가 성공(fulfilled)하였는지 또는 실패(rejected)하였는지 등의 상태(state) 정보를 갖는다.
- Promise는 비동기 함수를 실행할 콜백 함수를 인자로 전달 받고 이 콜백함수는 resolve와 reject함수를 매개변수로 같는다.
- pending, fulfilled, rejected 상태를 가지고 각각 비동기처리가 수행전, 성공, 실패인 상태를 뜻함. 즉 resolve,reject함수가 호출되거나 안된상태이다.
# Promise 객체 생성시 발생하는 일
1. Promise는 인스턴스 생성처럼 new 키워드를 통해 하나의 객체를 생성한다.
2. Promise는 하나의 콜백함수(비동기 콜백함수가 아님)를 인자로 받는다.
3. new Promise가 생성되는 즉시인자로 받아지는 함수도 즉시 실행되며,그래서 이 함수를 executor, 실행자 함수라고도 부른다.
3.5 executor는 사용자가 직접 구현을 해서 resolve나 reject를 실행하도록 만들수 있다.
const { readFile } = require("fs"); const getDataFromFilePromise = filePath => { return new Promise((resolve, reject) => { readFile(filePath, "utf8", function (err, text) { if (err) { reject(err); } else { resolve(text); } }); }); }
4. 해당 실행자(executor) 함수는 또 다시 2개 함수(resolve, reject)를 인자로 받는다.
5. 실행자 함수가 실행되고 작업이 성공했을 시에는 그 성공 값을 인자로 resolve 함수를 호출하고,만약 비동기 작업이 실패했을 시에는 그 실패 값을 인자로 reject 함수를 호출한다.
- Promise의 처리 메소드
- Promise.prototype.catch() : 반환된 Promise의 메서드가 실패했을때의 경우만 반환
- Promise.prototype.then() : 인자는 2개를 받고 각각 Promise객체의 결과가 resolve일때, reject일 경우 이다.
- then()의 반환값이 값인경우엔 Promise.resolve(<핸들러에서 반환한 값>) 을 반환하는 것과 같다.
- then()의 반환값이 Promise인경우엔 Promise로 감싸지않고 반환한다.
- catch(), then()의 return은 Promise를 반환한다. (Promise체인에 활용)
const getDataFromFilePromise = filePath => { return new Promise((resolve, reject) => {
fs.readFile(filePath,"utf-8",(err,data)=>{
if(err) reject(err);
else resolve(data);
});
- Promise의 에러처리
- then()이나 catch()를 통해서 에러처리를 하는데 then()에서의 에러처리는 해당 Promise를 다루는 도중에만 에러처리가 가능하다. then체인 마지막에 catch를 넣어주면 then에서 일어나는 에러를 catch에서 잡을수 있다. 물론 가독성도 훨씬 좋아진다.
promiseAjax('https://jsonplaceholder.typicode.com/todos/1')
.then(res => console.xxx(res))
.catch(err => console.error(err)); // TypeError: console.xxx is not a function
- Promise chaining
- 비동기 함수의 처리결과를 가지고 다른 비동기함수를 호출해야하는 경우 함수의 호출이 중첩되어 '콜백헬'같은 형태가 나타난다. 마찬가지로 Promise도 'Promise hell'형태를 띌수 있다.
/* lexical scope 활용 */
getDataFromFilePromise(user1Path)
.then((user1) => {
return getDataFromFilePromise(user2Path)
.then(user2 => {
return [JSON.parse(user1), JSON.parse(user2)];
})
}) //promise hell을 해결하기 위해 Promise.all이 등장(여러가지 이유중 하나)
- then()의 콜백함수의 비동기적 실행
- then과 catch를 사용하여 Promise객체를 받고 then의 콜백함수(핸들러 함수)를 실행할때 전달인자로 받을 수 있다.
- 전달인자를 받고 실행되는 핸들러 함수는 비동기적으로 실행이 된다.
- 여기서 말하는 핸들러 함수는 then이나 catch의 리턴값이다.
// 이행한 프로미스를 받으면 'then' 블록도 바로 실행되지만,
// 핸들러는 아래 console.log에서와 같이 비동기적으로 실행됨
const resolvedProm = Promise.resolve(33);
let thenProm = resolvedProm.then(value => {
console.log("이 부분은 호출 스택 이후에 실행됩니다. 전달받은 값이자 반환값은 " + value + "입니다.");
return value;
});
// thenProm의 값을 즉시 기록
console.log(thenProm);
// setTimeout으로 함수 실행을 호출 스택이 빌 때까지 미룰 수 있음
setTimeout(() => {
console.log(thenProm);
});
- Promise.all
- 해당메서드의 인자로 Promise객체를 요소로하는 배열을 받는다.
- 전달받은 배열에있는 Promise객체를 병렬로 처리하고 resolve값을 배열로 반환한다.
- 모든 Promise객체가 종료될때까지 기다렸다가 결과를 반환하고 하나라도 실패가있으면 reject를 반환한다.
- 호출한순서대로 순서가 보장된 객체를 반환한다.
/*
Promise.all
전달인자 Promise 객체 배열
반환 값은 resolve()값들이 들어있는 배열
fetch의 반환값은 Promise이고 해당 Promise가 실행되어 성공시 Response객체를 반환함.
json()의 반환값은 Promise객체
*/
const newsURL = "뉴스관련URL"
const weatherURL = "날씨관련URL"
return Promise.all([
fetch(newsURL),
fetch(weatherURL)
])
.then(([newsResponse, weatherResponse]) => {
return Promise.all([newsResponse.json(), weatherResponse.json()])
})
.then(([json1, json2]) => {
return {
news: json1.data,
weather: json2
}
/* 또는 */
let urls = [newsURL,weatherURL];
let temp = urls.map((url)=>{return fetch(url)});
Promise.all(temp)
.then(responses => { return responses.map((response => {return response.json()}));})
.then((arr) => { return Promise.all(arr)})
.then(resultarr =>{ console.log(resultarr); return resultarr})
let names = ['iliakan', 'Violet-Bora-Lee', 'jeresig'];
let requests = names.map(name => fetch(`https://api.github.com/users/${name}`));
Promise.all(requests)
.then(responses => { //responses에는 fetch의 return값인 Response객체들이 있음
// 모든 응답이 성공적으로 이행되었습니다.
for(let response of responses) {
alert(`${response.url}: ${response.status}`); // 모든 url의 응답코드가 200입니다.
}
return responses;
})
// 응답 메시지가 담긴 배열을 response.json()로 매핑해, 내용을 읽습니다.
.then(responses => Promise.all(responses.map(r => r.json())))
// JSON 형태의 응답 메시지는 파싱 되어 배열 'users'에 저장됩니다.
.then(users => users.forEach(user => alert(user.name)));
- Promise.all(promises) – 모든 프라미스가 이행될 때까지 기다렸다가 그 결과값을 담은 배열을 반환합니다. 주어진 프라미스 중 하나라도 실패하면 Promise.all는 거부되고, 나머지 프라미스의 결과는 무시됩니다.
- Promise.allSettled(promises) – 최근에 추가된 메서드로 모든 프라미스가 처리될 때까지 기다렸다가 그 결과(객체)를 담은 배열을 반환합니다. 객체엔 다음과 같은 정보가 담깁니다.
- status: "fulfilled" 또는 "rejected"
- value(프라미스가 성공한 경우) 또는 reason(프라미스가 실패한 경우)
- Promise.race(promises) – 가장 먼저 처리된 프라미스의 결과 또는 에러를 담은 프라미스를 반환합니다.
- Promise.resolve(value) – 주어진 값을 사용해 이행 상태의 프라미스를 만듭니다.
- Promise.reject(error) – 주어진 에러를 사용해 거부 상태의 프라미스를 만듭니다.
# async, await
- async 함수를 실행하게 되면 무조건 Promise 객체가 반환된다.
- async 함수 내에서 return값은 반환된 Promise 객체의 결과(resolve)값이다.
- await는 Promise함수가 fulfill되거나 reject될때 까지 async함수를 멈추고 기다림
- await는 Promise객체를 리턴할 경우에만 의미가 있음
- await의 반환값은 Promise를 실행했을 때 resolve의 전달인자값이다.
- Promise가 reject되면 reject된 값을 throw함. await함수를 호출하는 쪽(async가 선언된곳)에서 try catch로 오류를 잡음
- Promise의 syntactic sugar임
'WEB > Javascript' 카테고리의 다른 글
[JavaScript] 객체지향의 특징과 클래스 객체 인스턴스 차이 (0) | 2022.07.22 |
---|---|
[JavaScript] 프로토타입 (prototype) (0) | 2022.07.20 |
[JavaScript] This, new 키워드 (0) | 2022.07.16 |
[JavaScript] Event에 관하여 (0) | 2022.07.15 |
[JavaScript] DocumentFragment 장점 (0) | 2022.07.15 |