🙄async/await 문법
제너레이터보다 간단하고 가독성 좋게 비동기 처리를 동기 처리처럼 구현할 수 있는 문법입니다.
async/await를 이용하면 프로미스를 기반으로 동작하지만 프로미스 후속 메서드 없이도
마치 동기 처리처럼 프로미스를 사용할 수 있습니다.
async 함수는 항상 프로미스를 반환합니다.
await 키워드는 항상 async 함수 내부에서 사용해야하며 async함수는 대충 이렇게 사용합니다.
async function fetchTodo() { const response = await fetch(url) }
function 앞에 async를 쓰는걸로 할 수 있네요
// async 함수 선언문 async function foo(n) {return n;} foo(1).then(v => console.log(v)) // 1 // async 함수 표현식 const bar = async function (n) {return n} bar(2).then(v => console.log(v)) // 2 // async 화살표 함수 const baz = async n => n; baz(3).then(v => console.log(v)) // 3 // async 메서드 const obj = { async foo (n) { return n} } obj.foo(4).then(v => console.log(v)) // 4 // async 클래스 메서드 class MyClass { async bar(n) {return n} } //class의 constructor 메서드는 async 메서드가 될 수 없습니다. //constructor는 항상 인스턴스를 반환해야하지만 async는 항상 프로미스를 반환하기 때문입니다. const myClass = new MyClass(); myClass.bar(5).then(v => console.log(v)) // 5
🙄 await 키워드
await 키워드는 프로미스가 비동기 처리가 수행된 상태가 될때까지 대기하다
비동기 처리가 수행된 상태가되면 프로미스가 resolve 한 처리 결과를 반환합니다.
따라서 await 키워드는 반드시 프로미스 앞에서 사용해야 합니다.
async function foo() { const a = await new Promise(resolve => setTimeout(() => resolve(1) , 3000)) const b = await new Promise(resolve => setTimeout(() => resolve(2) , 2000)) const c = await new Promise(resolve => setTimeout(() => resolve(3) , 1000)) console.log([a,b,c]) } foo() /* [Done] exited with code=1 in 0.084 seconds [Running] [ 1, 2, 3 ] [Done] exited with code=0 in 6.109 seconds */
모든 프로미스에 await을 사용하는건 조심해야합니다.
위 예제는 총 6초가 소요됩니다. 하지만 저 a,b,c는 서로에게 영향을 주는게 아니기 때문에
굳이 동기처리처럼 처리할 필요가 없을 것에요!
async function bar(n) { const a = await new Promise(resolve => setTimeout(() => resolve(n),3000)) const b = await new Promise(resolve => setTimeout(() => resolve(a + 1),2000)) const c = await new Promise(resolve => setTimeout(() => resolve(b + 1),1000)) console.log([a,b,c]) } bar(1)
이런 경우엔 await을 통해서 처리순서를 보장해줘야 할 것입니다.
근데.. 이거 그냥 프로미스 후속 처리 메서드로 체이닝하는거랑 비슷한거 아닌가?
궁금해서 MDN의 async function에 대한 설명을 찾아봤습니다.
await 과 then과 같은 후속처리메서드의 가장 큰 차이점은
await은 하나의 프로미스가 결과를 반환할때까지 모든 프로미스들이 멈추지만
then을 이용하면 두개이상의 프로미스를 동시에 wait 시킬수 있다는 것.. 뭔소리냐고요?
저도 머리로 이해는 되는데 설명을 제대로 못하겠음
🙄에러처리
비동기처리를 위한 콜백 패턴의 단점은 에러처리가 곤란한 것입니다.
에러는 호출자 방향으로 전파됩니다.
하지만 비동기 함수의 콜백 함수를 호출한 것은 비동기 함수가 아니기 때문에
try..catch 문으로 에러를 캐치할 수 없습니다.
하지만 async/await 에서는 try..catch 문을 이용해 에러를 처리할 수 있습니다.
프로미스를 반환하는 비동기 함수는 명시적으로 호출할 수 있기 때문에 호출자가 명확하기 때문입니다.
const fetch = require('node-fetch') const foo = async() => { try { const wrongUrl = 'https://wrong.url' const response = await fetch(wrongUrl) const data = await response.json() console.log(data) } catch (err) { console.error(err) } } foo() /* 위 예제의 foo 함수의 catch문은 네트워크 에러뿐만 아니라 try 코드 블록 내의 모든 문에서 발생한 에러를 캐치할 수 있습니다. async 함수 내에서 catch 문을 사용해서 에러처리를 하지 않을 경우 async 함수는 발생한 에러를 reject 하는 프로미스를 반환합니다 따라서 catch 후속처리 메서드를 이용해서 처리하는것도 가능합니다. */
🙄try...catch...finally 문
에러 처리 방법에는 크게 두가지 종류가 있습니다.
하나는 예외적인 상황이 발생하면 보통 반환하는 -1이나 null같은 값을
if문, 옵셔널체이닝연산자 등을 이용해 확인해서 처리하는 방법이 있고
또 하나는 try,catch문을 이용하는 것입니다.
일반적으로 try,catch 문을 사용하는 걸 에러처리라고 한다네요
try..catch문을 이용하면 먼저 try 코드 블록이 실행되고
try 코드에서 에러가 발생하면 catch 문에 매개변수로 에러객체가 전달된 뒤 catch 블록이 실행됩니다.
try..catch 문으로 에러처리를 하면 프로그램이 강제 종료되지 않는다는 장점이 있습니다.
🙄Error 생성자 함수와 에러 객체
Error 생성자 함수는 에러 객체를 생성합니다.
Error 생성자 함수에는 에러를 상세하게 설명하는 에러 메시지를 인수로 전달할 수 있습니다.
자바스크립트는 Error 생성자 함수를 포함해
7가지의 에러 객체를 생성할 수 있는 Error 생성자 함수를 제공합니다.
또한 에러 객체의 프로토타입은 모두 Error.prototype을 상속받습니다.
🙄throw문
Error 생성자 함수로 에러 객체를 생성한다고 에러가 발생하는 건 아닙니다.
에러 객체의 생성과 에러 발생은 다릅니다.
에러를 발생시키기 위해서는 try 코드 블록에서
throw문으로 에러 객체를 던져야 합니다.
throw 문의 표현식은 어떤 값이라도 상관없지만
일반적으로는 에러 객체를 지정해줍니다.
에러를 던지면 catch 코드 블록이 실행되기 시작합니다.
const repeat = (n,f) => { if(typeof f !== 'function') throw new TypeError ('f must be a function') for(var i = 0 ; i<n ; i++) { f(i) } } try { repeat(2,1) } catch (err) { console.error(err) } //f must be a function
두번째 인자로 함수를 받아야하는 repeat함수를 작성하고
try문 안에서 두번째 인자에 함수를 넣지 않은 채 호출했습니다.
repeat 함수는 typeError를 throw하고 catch 문으로 넘어가 에러를 콘솔에 찍습니다.
🙄에러의 전파
에러는 호출자 방향으로 전파됩니다.
이것은 콜 스택의 아래 방향으로 에러가 전파된다는 뜻입니다.
const foo = () => { throw Error('foo에서 발생한 에러') } const bar = () => { foo() } const baz = () => { bar() } try { baz() } catch (err){ console.error(err) } /* Error: foo에서 발생한 에러 at foo (c:\Users\qdv16\OneDrive\바탕 화면\DeepDiveJs\47_Error\Error_에러전파.js:2:11) at bar (c:\Users\qdv16\OneDrive\바탕 화면\DeepDiveJs\47_Error\Error_에러전파.js:6:5) at baz (c:\Users\qdv16\OneDrive\바탕 화면\DeepDiveJs\47_Error\Error_에러전파.js:10:5) at Object.<anonymous> (c:\Users\qdv16\OneDrive\바탕 화면\DeepDiveJs\47_Error\Error_에러전파.js:14:5) at Module._compile (node:internal/modules/cjs/loader:1159:14) at Module._extensions..js (node:internal/modules/cjs/loader:1213:10) at Module.load (node:internal/modules/cjs/loader:1037:32) at Module._load (node:internal/modules/cjs/loader:878:12) at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:81:12) at node:internal/main/run_main_module:23:47 */
1. baz를 호출하면 bar가 호출되고 bar는 foo를 호출합니다.
2. foo에서 에러를 throw하면 foo에서 발생한 에러가 bar로 전파되고 baz로 전파됩니다.
이렇게 에러는 콜스택의 아래로 즉 호출자 방향으로 전파되고
throw 된 에러를 캐치해서 대응하지 않으면 프로그램은 강제종료됩니다.
중요한 점은 비동기 함수 setTimeout이나 프로미스 후속 처리 메서드의 콜백 함수는
호출자가 없다는 것입니다. 이런 함수는 콜스택이 비었을때
이벤트 루프에 의해 콜스택으로 푸시되어 실행되기 때문에
에러를 전파할 호출자가 존재하지 않기 때문입니다.
반응형
'javascript' 카테고리의 다른 글
두 배열이 동등한지 비교하는 방법 (0) | 2023.01.30 |
---|---|
요약 정리는 못 참지 않을까요? (20) 모듈 (1) | 2023.01.28 |
요약 정리는 못 참지 않을까요? (18) 제너레이터와 async/await (0) | 2023.01.27 |
요약 정리는 못 참지 않을까요? (17) Rest API와 프로미스 (2) | 2023.01.20 |
요약 정리는 못 참지 않을까요? (16) ajax와 json (0) | 2023.01.20 |