-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy path11-5.html
316 lines (286 loc) · 10.3 KB
/
11-5.html
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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
<!DOCTYPE html>
<html lang="zh-TW">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="icon" href="./public/favicon.ico" />
<meta http-equiv="cache-control" content="no-cache" />
<title></title>
<link rel="stylesheet" href=" https://necolas.github.io/normalize.css/8.0.1/normalize.css" />
<link rel="stylesheet" href="./hightlight/default.min.css" />
<link rel="stylesheet" href="./css/main.css" />
<link rel="stylesheet" href="./css/copybutton.css" />
<link rel="stylesheet" href="./css/hightlight.css" />
<script src="./hightlight/hightlight.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/clipboard.js/2.0.11/clipboard.min.js"></script>
<!-- Google tag (gtag.js) -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-BEVZJDBC7Z"></script>
<script src="./js/gtag.js"></script>
</head>
<body>
<header>
<nav>
<h1>
<span id="toggle-menu"></span>
<a href="index.html"></a>
</h1>
</nav>
</header>
<main>
<aside>
<nav></nav>
</aside>
<article>
<h2 id="11-5">11-5 Async and Await</h2>
<h3>說明:</h3>
<p>
await 通常用於拆開 promise 的包裝,使用方法是傳遞一個 Promise 作為 expression。使用 await
總會暫停當前異步函數的執行,在該 Promise 敲定 (settled , 指兌現或拒絕)
後繼續執行。函數的執行恢覆 (resume) 時, await 表達式的值已經變成了 Promise 兌現的值。若該
Promise 被拒絕 (rejected) , await 表達式會把拒絕的原因拋出。當前函數(await
所在的函數)會出現在拋出的錯誤的棧追蹤 (stack trace)
,否則當前函數就不會在棧追蹤出現。await 總會同步地對表達式求值並處理,處理的行為與
Promise.resolve() 一致,不屬於原生 Promise 的值全都會被隱式地轉換為 Promise
實例後等待。處理的規則為,若表達式:
</p>
<ul>
<li>
。 是一個原生 Promise(原生 Promise 的實例或其派生類的實例,且滿足 expression.constructor
=== Promise),會被直接用於等待,等待由原生代碼實現,該物件的 then()不會被調用。
</li>
<li>
。 是 thenable 物件(包括非原生的 Promise 實例、polyfill、Proxy、派生類等),會構造一個新
Promise 用於等待,構造時會調用該物件的 then() 方法。
</li>
<li>
。 不是 thenable 物件,會被包裝進一個已兌現的 Promise 用於等待,其結果就是表達式的值。
</li>
</ul>
<h3>等待 Promise 的兌現</h3>
<p>
當一個 Promise 被傳遞給 await 操作符, await 將等待該 Promise 兌現,並在兌現後回傳該
Promise 兌現的值。/p>
</p>
<pre><code class="language-js">
function resolveAfter2Seconds(x) {
return new Promise(resolve => {
setTimeout(() => {
resolve(x);
}, 2000);
});
}
async function f1() {
let x = await resolveAfter2Seconds(10);
console.log(x); // 10
}
f1();
</code></pre>
<h3>轉換為 promise</h3>
<p>若表達式的值不是 Promise , await 會把該值轉換為已兌現的 Promise ,然後回傳其結果。</p>
<pre><code class="language-js">
async function f3() {
const y = await 20;
console.log(y); // 20
const obj = {};
console.log((await obj) === obj); // true
}
f3();
</code></pre>
<h3>promise 被拒絕</h3>
<p>如果 Promise 被拒絕,則拋出拒絕的原因。</p>
<pre><code class="language-js">
async function f4() {
try {
const z = await Promise.reject(30);
} catch (e) {
console.error(e); // 30
}
}
f4();
</code></pre>
<h3>處理被拒絕的 promise</h3>
<p>
你可以鏈式調用 catch() (而不是使用 try)以在等待 promise 兌現之前處理被拒絕的 promise。
</p>
<pre><code class="language-js">
const response = await promisedFunction().catch((err) => {
console.error(err);
return 'default response';
});
// response will be "default response" if the promise is rejected
</code></pre>
<h3>在頂層使用 await</h3>
<p>
在模組的頂層,你可以單獨使用關鍵字 await (異步函數的外面)。也就是說一個模組如果包含用了
await 的子模組,該模組就會等待該子模組,這一過程並不會阻塞其他子模組。
</p>
<p>
下面是一個在 export 表達式中使用了 Fetch API
的例子。任何文件只要導入這個模組,後面的代碼就會等待,直到 fetch 完成。
</p>
<pre><code class="language-js">
// fetch request
const colors = fetch('../data/colors.json').then((response) => response.json());
export default await colors;
</code></pre>
<h3>await 對執行過程的影響</h3>
<p>
當函數執行到 await
時,被等待的表達式會立即執行,所有依賴該表達式的值的代碼會被暫停,並推送進微任務佇列
(microtask
queue)。然後主線程被釋放出來,用於事件迴圈中的下一個任務。即使等待的值是已經敲定的 promise
或不是 promise,也會發生這種情況。例如,考慮以下代碼:
</p>
<pre><code class="language-js">
async function foo(name) {
console.log(name, 'start');
console.log(name, 'middle');
console.log(name, 'end');
}
foo('First');
foo('Second');
// First start
// First middle
// First end
// Second start
// Second middle
// Second end
</code></pre>
<p>對應到 Promise 的寫法是:</p>
<pre><code class="language-js">
function foo(name) {
return new Promise((resolve) => {
console.log(name, 'start');
console.log(name, 'middle');
console.log(name, 'end');
resolve();
});
}
</code></pre>
<p>執行到 await 時,後面的代碼就會整體被安排進一個新的微任務,此後的函數體變為異步執行。</p>
<pre><code class="language-js">
async function foo(name) {
console.log(name, 'start');
await console.log(name, 'middle');
console.log(name, 'end');
}
foo('First');
foo('Second');
// First start
// First middle
// Second start
// Second middle
// First end
// Second end
</code></pre>
<p>對應到 Promise 的寫法是:</p>
<pre><code class="language-js">
function foo(name) {
return new Promise((resolve) => {
console.log(name, 'start');
resolve(console.log(name, 'middle'));
}).then(() => {
console.log(name, 'end');
});
}
</code></pre>
<p>
雖然這里的 then() 看起來很多餘,其中的代碼完全可以被合並到構造器的回呼裡,但不管該 Promise
的狀態如何,then() 的回呼總會被異步執行,await
的行為也一樣。因此,只要情況不是必須或可能需要等待 Promise 的結果,就應該避免使用 await。
</p>
<p>其他微任務能在函數執行恢復之前執行:</p>
<pre><code class="language-js">
let i = 0;
queueMicrotask(function test() {
i++;
console.log('microtask', i);
if (i < 3) {
queueMicrotask(test);
}
});
(async () => {
console.log('async function start');
for (let i = 1; i < 3; i++) {
await null;
console.log('async function resume', i);
}
await null;
console.log('async function end');
})();
queueMicrotask(() => {
console.log('queueMicrotask() after calling async function');
});
console.log('script sync part end');
// Logs:
// async function start
// script sync part end
// microtask 1
// async function resume 1
// queueMicrotask() after calling async function
// microtask 2
// async function resume 2
// microtask 3
// async function end
</code></pre>
<p>
此案例中,test()
總會在異步函數恢復執行前被調用,呈現輪流的調度。微任務被執行的順序通常就是入列的先後順序,而
console.log("queueMicrotask() after calling async function"); 比 await 晚入列,因此
"queueMicrotask() after calling async function" 在異步函數第一次恢復之後才輸出。
</p>
<h3>改善棧追蹤</h3>
<p>有時,當異步函數直接回傳一個 Promise 時我們會省略 await。</p>
<pre><code class="language-js">
async function noAwait() {
// Some actions...
return /* await */ lastAsyncTask();
}
</code></pre>
<p>但是假如這個 Promise 的由來是調用了異步函數,且該異步函數的異步部分拋出了錯誤:</p>
<pre><code class="language-js">
async function lastAsyncTask() {
await null;
throw new Error("failed");
}
async function noAwait() {
return lastAsyncTask();
}
noAwait();
// Error: failed
// at lastAsyncTask
</code></pre>
<p>
棧追蹤中只出現了 lastAsyncTask,這是因為拋出錯誤時 noAwait 已經回傳——某種意義上該 Promise
已經與 noAwait 無關。若要改善棧追蹤,你可以用 await
提前等待,錯誤就會在函數體結束前拋出,接著該錯誤會被包裝進一個新的 Promise,因錯誤被 await
在主調函數的函數體拋出,主調函數將會出現在棧追蹤。
</p>
<p>但是,這樣會有一點性能犧牲,畢竟 Promise 會被拆裝了又再次包裝。</p>
<pre><code class="language-js">
async function lastAsyncTask() {
await null;
throw new Error('failed');
}
async function withAwait() {
return await lastAsyncTask();
}
withAwait();
// Error: failed
// at lastAsyncTask
// at async withAwait
</code></pre>
<p>資料來源:</p>
<p>
<a
href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/await"
>
https://developer.mozilla.org/en-US/docs/Web/JavaScript/...
</a>
</p>
</article>
</main>
</body>
<script type="module" src="./js/main.js"></script>
</html>