JavaScript
20210721 JavaSciprt DeepDive 17 : 프로미스, fetch, 제너레이터, async/await, 에러 처리
JavaScript Deep Dive 17
📄 용어 및 중요사항 정리
프로미스
비동기 함수
: 함수 내부에 비동기로 동작하는 코드를 포함한 함수- 비동기 함수 내부의 비동기 동작 코드는 비동기 함수가 종료된 이후에 완료됨
- 비동기 동작 코드에서 처리 결과를 외부로 반환하거나, 상위 스코프의 변수에 할당 하면 기대한 대로 동작하지 않음
- 비동기 함수의 처리 결과에 대한 후속 처리는 비동기 함수 내부에서 외부에서 콜백 함수를 받아 수행해야 함 -> 콜백 헬 발생
- 콜백 헬
- 에러 처리 한계 : 에러는 호출자 방향으로 전파되지만, 비동기 함수의 콜백은 비동기 함수가 호출하지 않음 -> 콜백에 대한 에러는 catch 되지 않음
프로미스
:- 콜백 헬을 극복하기 위해 도입됨
- 표준 빌트인 객체
- 비동기 처리 상태와 철리 결과를 관리하는 객체
- new Promise 생성자 함수로 생성
- 비동기 처리를 수행할 콜백 함수를 인수로 전달 받음
- 비동기 처리 후속 콜백 resolve(성공), reject(실패) 콜백 함수를 인수로 받음
- 비동기 상태 정보 (status)
- pending : 비동기 처리가 수행되지 않은 상태
- settled : fulfilled(성공), rejected(실패)
- 비동기 결과 (result)
- undefined -> value or error
const promise = new Promise((resolve, reject) => {
if (/* 비동기 처리 성공 조건 */) {
// 비동기 성공시 호출할 함수
resolve('result')
} else {
// 비동기 실패시 호출할 함수
reject('failure reason')
}
})
프로미스의 후속 처리 메서드
Promise.prototype.then(resolveCallback, rejectedCallback)
- resolveCallback : fulfilled 상태시 프로미스의 비동기 처리 결과를 인수로 받음
- rejectedCallback : rejected 상태시 프로미스의 에러를 인수로 받음
- return : 언제나 프로미스 반환
Promise.prototype.catch(callback)
- rejected 상태인 경우 호출 됨
- callback : error 처리
- return : 언제나 프로미스 반환
Promise.prototype.finally(callback)
- 프로미스 성공 실패 상관 없이 무조건 한번 호출
- return : 언제나 프로미스 반환
프로미스의 에러 처리
- 방법01 : then 메서드의 두번째 메서드로 에러 처리
- 프로미스 체이닝시 다른 then 메서드에서 발생하는 에러는 캐치하지 못하여, 코드 복잡해지고 가독성이 떨어짐
- 방법02 : catch 메서드 (추천)
- 모든 then 메서드 호출한 이후 호출시 비동기 처리 에러, then 메서드 내부의 에러까지 모두 캐치 가능
프로미스 체이닝
- then, catch, finally 후속 처리 메서드는 언제나 프로미스를 반환하므로, 연속 호출 가능 함
- 후속 처리 메서드의 콜백 함수가 프로미스가 아닌 값을 반환해도, 암묵적으로 resolve, reject하여 프로미스를 생성해 반환함
프로미스의 정적 메서드
Promise.resolve/reject(value)
: 이미 존재하는 값을 래핑하여 프로미스를 생성하기 위해 사용
// Promise.resolve/reject를 활용한 방식
const resolvePromise = Promise.resolve([1, 2, 3]);
resolvePromise.then(console.log); // [1, 2, 3]
const rejectPromise = Promise.reject(new Error("Error!"));
rejectedPromise.catch(console.log); // Error: Error!
// 기존 방식
const resolvePromise = new Promise((resolve) => resolve([1, 2, 3]));
resolvePromise.then(console.log); // [1, 2, 3]
const rejectPromise = new Promise((_, reject) => reject(new Error("Error!")));
rejectedPromise.catch(console.log); // Error: Error!
Promise.all([asyncFunc1, asyncFunc2, asyncFunc3, ...])
- 여러개의 비동기 처리를 모두 병렬 처리할 때 사용함
- 배열 등의 이터러블을 인수로 전달 받음
- 모든 프로미스가 모두 fulfilled 상태가 되면 모든 처리 결과를 배열에 저장해 새로운 프로미스를 반환함
- 처리한 결과 값은 넣은 비동기 처리 함수 순서 차례 대로 배열에 저장함(완료 순서X)
- 하나라도 rejected 상태가 되면 나머지 프로미스 fulfiled 상태를 기다리지 않고 즉시 종료 됨
- 이터러블의 요소가 프로미스가 아닌 경우, Promise.resolve 메서드로 프로미스로 래핑 함
- map 메서드를 통해서 여러 반복 작업을 비동기 적으로 처리 할 수 있음
Promise.race([asyncFunc1, asyncFunc2, asyncFunc3, ...])
- 배열 등의 이터러블을 인수로 전달 받음
- 가장 먼저 fulfilled 상태가 된 프로미스의 처리 결과를 resolve하는 새로운 프로미스를 반환
- 하나라도 rejected 상태가 되면 나머지 프로미스 fulfiled 상태를 기다리지 않고 즉시 종료 되고, 해당 rejected 프로미스를 반환
Promise.allSettled([asyncFunc1, asyncFunc2, asyncFunc3, ...])
- 배열 등의 이터러블을 인수로 전달 받음
- 전달 받은 프로미스가 모두 settled 상태가 되면 처리 결과를 배열로 반환함
- fulfilled, rejected 상태 상관 없이 모든 프로미스들의 처리 결과를 나타내는 객체가 담겨 있음
- 처리 결과 객체의 프로퍼티
- fulfilled -> status, value
- rejected -> status, reason
- 처리 결과 객체의 프로퍼티
마이크로 태스크 큐
- 후속 처리 메서드의 콜백 함수는 태스크 큐가 아니라 마이크로태스큐에 저장 됨
- 마이크로 태스크 큐는 태스크 큐와 별도의 큐 임
- 태스크 큐보다 우선순위가 높음
- 이벤트 루프는 마이크로 태스크 큐의 함수를 먼저 가져와서 실행함
- 마이크로 태스크 큐가 비면 그때 태스크 큐에서 함수를 가져와 실행 함
fetch
- XMLHttpRequest 객체와 마찬가지로 HTTP 요청 전송 기능을 제공하는 클라이언트 사이드 Web API
- fetch 함수는 사용법도 간단하고, 프로미스를 지원함
fetch(url [, options])
- HTTP 응답을 나타내는 Response 객체를 래핑한 Promise 객체를 반환함
- URL만 전달하면 GET 요청을 전송함
- then을 통해서 fatch 함수가 resolve한 Response 객체를 전달 받을 수 있음
- Response 객체의 prototype 메서드가 존재함
Response.prototype.json()
: MIME 타입 - application/json인 응답 몸체 취득- 취득시 역직렬화 시켜줌
url
: HTTP 요청을 전송할 URLoptions
: options 객체- method : 'POST', 'PATCH', 'DELETE'
- headers : {'accept or content-Type':'MIME type'}
- body : 요청 전송할 body
// fetch를 이용한 요청 함수 만들기
const request = {
get(url) {
return fetch(url);
},
post(url, payload) {
return fetch(url, {
method: "POST",
headers: { "content-Type": "application/json" },
body: JSON.stringify(payload),
});
},
patch(url, payload) {
return fetch(url, {
method: "PATCH",
headers: { "content-Type": "application/json" },
body: JSON.stringify(payload),
});
},
delete(url) {
return fetch(url, { method: "DELETE" });
},
};
제너레이터
제너레이터(generator)
: 코드 블록의 실행을 일시 중지했다가 필요한 시점에 재개할 수 있는 특수한 함수- 특징
- 함수 호출자에게 함수 실행의 제어권을 양도(yield)할 수 있음(함수 호출자가 함수 실행 제어 가능)
- 함수 호출자에게 상태를 전달할 수 있고 함수 호출자로부터 상태를 전달 받을 수도 있음
- 제너레이터 함수 호출시 이터러블이면서 동시에 이터레이터인 제너레이터 객체를 반환함 (함수 코드 실행 X)
- 특징
function*
,yield
사용- 화살표 함수로 정의 불가
- new 연산자와 함꼐 생성자 함수로 호출 불가
제너레이터 객체
- 제너레이터 객체는 이터러블이면서 동시에 이터레이터 임
- 제너레이터 객체는 next 메서드를 가지는 이터레이터 이므로 Symbol.iterator 메서드를 호출해서 별도로 이터레이터를 생성할 필요가 없음
제너레이터 객체 메서드
next()
: 제너레이터 함수의 yield 표현식 까지 코드 블록을 실행하고, yield된 값을 value 프로퍼티 값으로, false를 done 프로퍼티 값으로 갖는 이터레이터 result 객체를 반환- 인수를 넣으면 yield 표현식을 할당받는 변수에 할당 됨
- 처음 호출되는 next는 인수를 넣어도 무시됨
return(value)
: 인수로 전달받은 값을 value 프로퍼티 값으로, true를 done 프로퍼티 값으로 갖는 이터레이터 result 객체 반환throw(error)
: 전달 받은 에러를 발생시키고 undefined를 value 프로퍼티 값, true를 done 프로퍼티 값으로 갖는 이터레이터 result 객체를 반환
제너레이터의 일시 중지와 재개
- yield 키워드와 next 메서드를 통해 실행 중지와 재개를 할 수 있음
- next 메서드 호출시 yield 표현식 까지 실행되고 일시 중지 됨 -> 함수 제어권 호출자로 양도
- next 호출시 반환된 result 객체에는 yield 표현식 뒤에 값이 value 프로퍼티에 할당되고, done 프로퍼티에 제너레이터 함수가 끝까지 실행 되었는지 불리언 값 할당
function* getFunc() {
const x = yield 1;
const y = yield x + 10;
return x + y;
}
const generator = getFunc();
let res = generator.next();
console.log(res); // {value: 1, done: false}
res = getnerator.next(10);
console.log(res); // {value: 20, done: false}
res = getnerator.next(20);
console.log(res); // {value: 30, done: true}
제너레이터의 활용
간단한 이터러블의 구현
// 기존 이터러블 구현
const infiniteFibonacci = (function () {
let [pre, cur] = [0, 1]
return {
[Symbol.iterator]() { return this}
next() {
[pre, cur] = [cur, pre + cur];
return {value: cur}
}
}
})()
// 제너레이터를 이용한 이터러블 구현
const infiniteFibonacci = (function* () {
let [pre, cur] = [0, 1]
while (true) {
[pre, cur] = [cur, pre + cur]
yield cur
}
}())
비동기 처리
- 비동기 처리를 동기 처리 처럼 구현 가능
- 제너레이터 실행기가 필요하면 co 라이브러리 사용 추천
// 제너레이터 실행기
const async = (generatorFunc) => {
const generator = generatorFunc();
const onResolved = (arg) => {
const result = generator.next(arg);
return result.done
? result.value
: result.value.then((res) => onResolved(res));
};
return onResolved;
};
async(function* fetchTodo() {
const url = "https://jsonplaceholder.typicode.com/todos/1";
const response = yield fetch(url);
const todo = yield response.json();
console.log(todo);
})();
async/await
- 후속 처리 메서드에 콜백 함수를 전달해서 비동기 처리 결과를 후속으로 처리할 필요 없이 동기 처리 처럼 프로미스를 사용할 수 있게 해줌
async 함수
- async 키워드를 사용해 정의한 함수로, 언제나 프로미스를 반환 함
- 명시적으로 프로미스를 반환하지 않더라도 암묵적으로 resolve하는 프로미스를 반환함
// async 함수 선언문
async function foo(n) {
return n;
}
// async 함수 표현식
const bar = async function (n) {
return n;
};
// async 화살표 함수
const baz = async (n) => n;
// async 메서드
const obj = {
async foo(n) {
return n;
},
};
// async 클래스 메서드
class MyClass {
async bar(n) {
return n;
}
}
await 키워드
- 프로미스가 settled 상태가 될 때까지 대기하다가 settled 상태가 되면 resolved한 처리 결과 반환
- await는 프로미스 앞에서 사용해야 함
- 서로 연관 되지 않은 프로미스의 경우 시간을 더 잡아 먹으므로 ,
await Promise.all
을 사용해 처리함
에러 처리
- 원래 비동기 함수의 콜백 함수를 호출한 것은 비동기 함수가 아니기 때문에 try...catch 문을 사용해 에러를 캐치할 수 없음
- async/await 에서 try...catch 문으로 에러를 모두 캐치 할 수 있음
- 프로미스를 반환하는 비동기 함수는 명시적으로 호출 가능하여 호출자가 명확함
- async 함수 내에서 catch 문을 사용하지 않으면, async 함수는 발생한 에러를 reject하는 프로미스를 반환함
에러 처리
- 에러가 발생하지 않는 코드를 작성하는 것은 불가능 하고, 에러 발생시 프로그램은 강제 종료 됨
- try...catch 문을 통해서 프로그램 강제 종료를 막을 수 있음
예외 처리
: 직접적으로 에러를 발생하지는 않는 상황- 예외적인 상황에 의해서 다른 코드가 에러를 발생할 수 있음
- 예) querySelector의 경우 없으면 null을 반환함 -> null을 할당 받은 변수에 프로퍼티를 참조하여 사용하는 경우 error 발생
- querySelector의 반환 값을 단축 평가 또는 옵셔널 체이닝 연산자 사용해 예외 처리 하거나, 에러 처리 코드를 미리 등록해 두고 에러 발생시 에러 처리 코드로 점프 되도록 하는 방법
try...catch...finally 문
- 에러 처리(error handling) : try...catch...finally 문을 사용하여 에러를 처리하는 것
- try : 에러가 발생할 가능성이 있는 코드 블럭
- catch : try에서 에러 발생시 실행되는 코드 블럭 (try에서 발생한 Error 객체가 전달됨)
- finally : 에러 발생과 상관 없이 반드시 한번 실행되는 코드 블럭
Error 객체
new Error(errorMessage)
- errorMessage: 에러를 상세히 설명하는 에러 메세지(string)를 인수로 받음
message 프로퍼티
: Error 생성자 함수에 전달한 인수를 값으로 갖는 프로퍼티stack 프로퍼티
: 에러를 발생시킨 콜스택의 호출 정보를 나타내는 문자열 (디버깅 목적)에러 객체 종류 7가지
- Error : 일반적인 에러 객체
- SyntaxError : JS 문법에 맞지 않는 문 해석시 발생하는 에러 객체
- ReferenceError : 참조 함수 없는 식별자를 참조시 발생하는 에러 객체
- TypeError : 피연산자 또는 인수의 데이터 타입이 유효하지 않을 때
- RangeError : 숫자값의 허용 범위를 벗어났을 때 발생
- URIError : encodeURI 또는 decodeURI 함수에 부적절한 인수 전달시 발생
- EvalError : eval 함수에서 발생하는 에러 객체
thorw 문
- 해당 에러 객체로 에러를 발생 시킴
- try 문에 error 발생시, catch 문에 error 변수가 생성되고 해당 error 객체가 할당됨
에러 전파
- throw된 에러를 캐치 하지 않으면, 에러는 호출자 방향으로 전파 됨 (콜 스택의 아래 방향으로 전파됨)
- 비동기 함수, 프로미스 후속 처리 메서드의 콜백 함수는 호출자가 없기 때문에 에러 전파가 되지 않음