|
| 1 | +# JS Promise |
| 2 | + |
| 3 | +- [JS Promise](#js-promise) |
| 4 | + - [Promise 생성](#promise-생성) |
| 5 | + - [async await](#async-await) |
| 6 | + - [마이크로태스크](#마이크로태스크) |
| 7 | + |
| 8 | +## Promise 생성 |
| 9 | + |
| 10 | +```js |
| 11 | +const p1 = new Promsie((resolve, reject) => { |
| 12 | + // resolve() // p1 객체는 이행됨(fulfilled) |
| 13 | + // reject() // p1 객체는 거부됨(rejected) |
| 14 | + // exception 발생 => 거부됨 |
| 15 | +}); // 대기중(pending) 상태 return Promise |
| 16 | +const p2 = Promise.reject(); // return Promise |
| 17 | +const p3 = Promise.resolve(); // reeturn Promise |
| 18 | +``` |
| 19 | + |
| 20 | +- new 키워드를 사용해서 프로미스를 생성하는 순간 생성자의 입력함수가 실행 |
| 21 | +- 만약 API 용청을 보내는 비동기 코드가 있다면 **프로미스가 생성되는 순간에** 요청을 보낸다. |
| 22 | + |
| 23 | +```js |
| 24 | +const p1 = Promise.resolve(123); // return Promise |
| 25 | +Promise.resolve(p1) === p1; // Promise.resolve 함수에 프로미스가 입력되면 그 자신이 반환된다. |
| 26 | +``` |
| 27 | + |
| 28 | +```js |
| 29 | +requestData().then(onResolve, onReject); // 프로미스가 처리됨 상태가 되면 onResolve 함수가 호출 되고, 거부됨 상태가 되면 onRejct 함수가 호출 |
| 30 | +Promise.resolve(123).then((data) => console.log(data)); |
| 31 | +Promise.reject(123).then(null, (error) => console.log(error)); |
| 32 | +``` |
| 33 | + |
| 34 | +- 결과적으로 `then` 메서드는 **항상 프로미스를 반환한다.** |
| 35 | +- 프로미스가 `거부됨 상태인` 경우에는 **onReject 함수가 존재하는 then을 만날때까지 이동한다.** |
| 36 | + |
| 37 | +```js |
| 38 | +Promise.reject(data) |
| 39 | + .then((then1) => console.log("then1", then1)) |
| 40 | + .then((then2) => console.log("then2", then2)) |
| 41 | + .then((then3) => console.log("then3", then3), (then4) => console.log("then4", then4)); |
| 42 | + .then((then5) => console.log("then5", then5), (then6) => console.log("then6", then6)); |
| 43 | +``` |
| 44 | + |
| 45 | +- 거부된 상태인 프로미스는 처음으로 만나는 onReject 함수를 호출하므로 빈 코드 블록은 생략 된다. |
| 46 | +- onReject 함수는 undefined를 결과로 가지면서 이행됨 상태인 프로미스를 생성한다. |
| 47 | +- 따라서 then5가 출력된다. |
| 48 | +- result: then4 ~ , then5 ~ |
| 49 | + |
| 50 | +```js |
| 51 | +Promise.reject(data).catch((error) => console.log("error", error)); // catch 메서드는 then 메서드의 onReject 함수와 같은 역할을 한다. |
| 52 | +Promise.reject(data) |
| 53 | + .catch((error) => error); // catch 메서드도 새로운 프로미스를 반환한다. |
| 54 | + .then((then) => console.log("then", then)) |
| 55 | +``` |
| 56 | + |
| 57 | +```js |
| 58 | +new Promise(function (resolve, reject) { |
| 59 | + setTimeout(() => { |
| 60 | + throw new Error("에러 발생!"); |
| 61 | + }, 1000); |
| 62 | +}).catch(alert); |
| 63 | +``` |
| 64 | + |
| 65 | +- `.catch는` 트리거 되지 않는다. |
| 66 | +- 암시적 try, .. catch가 함수 코드를 감사고 있으므로 모든 동기적 에러는 암시적 try, ...catch에서 처리 된다. |
| 67 | +- 하지만 여기에서 에러는 excutor(실행자, 실행 함수)가 실행되는 동안이 아니라 나중에 발생한다. |
| 68 | +- **따라서 프로미스는 에러를 처리할 수 없다.** |
| 69 | +- 하지만 `reject로` 호출했을 때는 `catch로` 전달이 된다. |
| 70 | + |
| 71 | +```js |
| 72 | +requestData() |
| 73 | + .then((data) => {}) |
| 74 | + .catch((error) => {}) |
| 75 | + .finally(() => {}); |
| 76 | +``` |
| 77 | + |
| 78 | +- finally는 처리됨 상태인 프로미스의 데이터를 건드리지 않고 추가 작업을 할 때 유용하게 쓰인다. |
| 79 | +- requestData 함수의 반환값은 finally 메서드 호출 이전의 프로미스다. |
| 80 | + |
| 81 | +```js |
| 82 | +Promise.all([requestData(), requestData2()]).then(([data1, data2]) => { |
| 83 | + console.log("data1", data1); |
| 84 | + console.log("data2", data2); |
| 85 | +}); // return Promise |
| 86 | +``` |
| 87 | + |
| 88 | +- `Promise.all` 함수가 반화하는 프로미스는 입력된 **모든 프로미스가 처리됨 상태가** 되어야 처림됨 상태가 된다. |
| 89 | +- 하나라도 거부됨 상태가 된다면 즉시, Promise.all 함수가 반환하는 프로미스도 거부됨 상태가 된다. |
| 90 | + > fetch를 사용해 호출 여러 개를 만들면, 그중 하나가 실패하더라도 호출은 계속 일어납니다. |
| 91 | + > 렇더라도 Promise.all은 다른 호출을 더는 신경 쓰지 않습니다. 프라미스가 처리되긴 하겠지만 그 결과는 무시됩니다. |
| 92 | +
|
| 93 | +```js |
| 94 | +Promise.race([ |
| 95 | + requestData(), |
| 96 | + new Promise((_, reject) => setTimeout(reject, 3000)), |
| 97 | +]) |
| 98 | + .then((data) => console.log(data)) |
| 99 | + .catch((error) => console.log(error)); |
| 100 | +``` |
| 101 | + |
| 102 | +- `Promise.race는` 여러 개의 프로미스 중에서 가장 빨리 처리된 프로미스를 반환하는 함수이다. |
| 103 | +- requestData 함수가 3초 안에 데이터를 받으면 then 메서드가 호출, 그렇지 않으면 catch 메서드 호출 |
| 104 | + |
| 105 | +```js |
| 106 | +function requestData(params) { |
| 107 | + const p = Promise.resolve(10); // 생성되자 마자 실행 => new reslovePromise(10) |
| 108 | + p.then(() => 20); // then 메서드는 기존 객체를 수정하지 않고, 새로운 프로미스를 반환한다. => new Thenalbe(()=> 20) |
| 109 | + return p; |
| 110 | +} |
| 111 | +requestData().then((v) => { |
| 112 | + console.log("v", v); // 10 |
| 113 | +}); |
| 114 | +``` |
| 115 | + |
| 116 | +- 프로미스는 `불변 객체다`. |
| 117 | + |
| 118 | +```js |
| 119 | +function requestData() { |
| 120 | + return Promise.resolve(10).then((v) => { |
| 121 | + return 20; |
| 122 | + }); |
| 123 | +} |
| 124 | +requestData().then((v) => { |
| 125 | + console.log("v", v); // 20 |
| 126 | +}); |
| 127 | +``` |
| 128 | + |
| 129 | +## async await |
| 130 | + |
| 131 | +```js |
| 132 | +async function getData() { |
| 133 | + return 123; |
| 134 | +} // return Promise |
| 135 | +getData().then((data) => console.log("data", data)); // 123 |
| 136 | +``` |
| 137 | + |
| 138 | +- `async는` Promise 객체로 변환하여 리턴해주는 키워드이다. |
| 139 | + |
| 140 | +```js |
| 141 | +async function getData() { |
| 142 | + return Promise.resolve(123); // 프로미스의 then 메서드와 마찬가지로 async await 함수 내부에서 반환하는 값이 프로미스라면 그 객체를 그대로 반환 |
| 143 | +} |
| 144 | +getData().then((data) => console.log(data)); // 123 |
| 145 | +``` |
| 146 | + |
| 147 | +```js |
| 148 | +function requestData(value) { |
| 149 | + return new Promise((resolve) => |
| 150 | + setTimeout(() => { |
| 151 | + console.log("requestData", value); |
| 152 | + resolve(value); |
| 153 | + }, 100) |
| 154 | + ); |
| 155 | +} |
| 156 | +async function getData() { |
| 157 | + const data1 = await requestData(10); // 프로미스가 처리됨 상태가 될 때까지 다음 코드를 실행하지 않는다. => .then 역할 |
| 158 | + const data2 = await requestData(20); |
| 159 | + console.log("data1", data1); |
| 160 | + console.log("data2", data2); |
| 161 | + return [data1, data2]; |
| 162 | +} |
| 163 | +getData(); // Promise |
| 164 | +``` |
| 165 | + |
| 166 | +```js |
| 167 | +async function getData() { |
| 168 | + const p1 = asyncFunc1(); |
| 169 | + const p2 = asyncFunc2(); |
| 170 | + // 두개의 프로미스가 생성되고 각자의 비동기 코드가 실행된다. |
| 171 | + const data1 = await p1; // await으로 프로미스가 생성된 후 기다리기 때문에 두 개의 비동기 함수가 병렬로 처리된다. |
| 172 | + const data2 = await p2; |
| 173 | +} |
| 174 | +async function getData() { |
| 175 | + const [data1, data2] = await Promise.all([asyncFunc1(), asyncFunc2()]); |
| 176 | +} |
| 177 | +``` |
| 178 | + |
| 179 | +```js |
| 180 | +class TEX { |
| 181 | + then(resolve, reject) { |
| 182 | + setTimeout(() => resolve(1234), 10000); |
| 183 | + } |
| 184 | +} |
| 185 | +async function asyncFunc() { |
| 186 | + const result = await new TEX(); |
| 187 | + console.log("result", result); // 1234 |
| 188 | +} |
| 189 | +``` |
| 190 | + |
| 191 | +- `Thenable은` 프로미스처럼 동작하는 객체다. |
| 192 | +- async await은 ES6의 프로미스가 아니더라도 then 메서드를 가진 객체를 **프로미스처럼 취급한다.** |
| 193 | +- 프로미스가 아니더라도 then 메서드르 가진 객체를 Thenable이라고 부른다. |
| 194 | + |
| 195 | +## 마이크로태스크 |
| 196 | + |
| 197 | +- 프로미스 핸들러 `.then/catch/finally는` 항상 비동기적으로 실행된다. |
| 198 | + > 프라미스가 즉시 이행되더라도 `.then/catch/finally` 아래에 있는 코드는 이 핸들러들이 실행되기 전에 실행됩니다. |
| 199 | +
|
| 200 | +```js |
| 201 | +let promise = Promise.resolve(); |
| 202 | + |
| 203 | +promise.then(() => console.log("프로미스 성공!")); |
| 204 | + |
| 205 | +console.log("코드 종료"); // 이 로그가 가장 먼저 나타난다. |
| 206 | +``` |
| 207 | + |
| 208 | +> 요약하자면, 어떤 프라미스가 준비되었을 때 이 프라미스의 `.then/catch/finally` 핸들러가 마이크로태스크큐에 들어간다고 생각하시면 됩니다. |
| 209 | +> |
| 210 | +> 이때 핸들러들은 여전히 실행되지 않습니다. `현재 코드에서 자유로운 상태가 되었을 때에서야` 자바스크립트 엔진은 큐에서 작업을 꺼내 실행합니다. |
| 211 | +
|
| 212 | +> 비동기 작업을 처리하려면 적절한 관리가 필요합니다. |
| 213 | +> |
| 214 | +> 이를 위해 ECMA에선 PromiseJobs라는 내부 큐(internal queue)를 명시합니다. |
| 215 | +> |
| 216 | +> V8 엔진에선 이를 '마이크로태스크 큐(microtask queue)'라고 부르기 때문에 이 용어가 좀 더 선호됩니다. |
| 217 | +
|
| 218 | +- JS_Engine 참고 |
| 219 | + |
| 220 | +```js |
| 221 | +let promise = Promise.reject(new Error("프로미스 실패!")); |
| 222 | +setTimeout(() => promise.catch((err) => alert("잡았다!")), 1000); |
| 223 | + |
| 224 | +// Error: 프로미스 실패! |
| 225 | +window.addEventListener("unhandledrejection", (event) => alert(event.reason)); |
| 226 | +``` |
| 227 | + |
| 228 | +- `unhandledrejection은` 마이크로태스크 큐에 있는 작업 모두가 완료되었을 때 생성된다. |
| 229 | + - `.catch`같이 `에러 헨들러가` 없다면 마이크로태스크 큐는 계속해서 에러 핸들러를 처리하고 요청을 하기 때문에 뒤에있는 테스크가 쌓이게 된다. |
| 230 | + - 그것을 맞기위해 `unhandlerrejection을` `트리거` 하여 에러를 처리하도록 요청한다. |
| 231 | +- 위 예시를 실행하면 setTimeout을 사용해 추가한 `.catch` 역시 트리거 된다. |
| 232 | + - 다만 `.catch는` `unhandledrejection이` 발생한 이후에 트리거 되므로 `프로미스 실패!가 출력된다.` |
0 commit comments