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
317
318
319
320
321
322
323
324
325
326
327
|
---
title: async函数
slug: Web/JavaScript/Reference/Statements/async_function
tags:
- JavaScript
- 函数
- 声明
- 异步函数
- 语言特性
translation_of: Web/JavaScript/Reference/Statements/async_function
---
<div>{{jsSidebar("Statements")}}</div>
<p>async函数是使用<code>async</code>关键字声明的函数。 async函数是{{jsxref("Global_Objects/AsyncFunction","AsyncFunction")}}构造函数的实例, 并且其中允许使用<code>await</code>关键字。<code>async</code>和<code>await</code>关键字让我们可以用一种更简洁的方式写出基于{{jsxref("Promise")}}的异步行为,而无需刻意地链式调用<code>promise</code>。</p>
<div class="noinclude">
<p>async函数还可以被{{jsxref("Operators/async_function", "作为表达式", "", 1)}}来定义。</p>
</div>
<div>{{EmbedInteractiveExample("pages/js/statement-async.html", "taller")}}</div>
<p class="hidden">该交互式demo源文件存储于Github仓库中。如果希望为此交互式项目做出贡献,请 clone <a href="https://github.com/mdn/interactive-examples">https://github.com/mdn/interactive-examples</a> 项目并用pull形式向我们的原始仓库发出请求。</p>
<h2 id="语法">语法</h2>
<pre class="syntaxbox">async function <em>name</em>([<em>param</em>[, <em>param</em>[, ... <em>param</em>]]]) {
<em> statements </em>
}
</pre>
<h3 id="参数">参数</h3>
<dl>
<dt><code>name</code></dt>
<dd>函数名称。</dd>
</dl>
<dl>
<dt><code>param</code></dt>
<dd>要传递给函数的参数的名称。</dd>
</dl>
<dl>
<dt><code>statements</code></dt>
<dd>包含函数主体的表达式。可以使用<code>await</code>机制。</dd>
<dt>
<h3 id="返回值">返回值</h3>
</dt>
<dd>一个{{jsxref("Promise")}},这个promise要么会通过一个由async函数返回的值被解决,要么会通过一个从async函数中抛出的(或其中没有被捕获到的)异常被拒绝。</dd>
</dl>
<h2 id="描述">描述</h2>
<p>async函数可能包含0个或者多个{{jsxref("Operators/await", "await")}}表达式。await表达式会暂停整个async函数的执行进程并出让其控制权,只有当其等待的基于promise的异步操作被兑现或被拒绝之后才会恢复进程。promise的解决值会被当作该await表达式的返回值。使用<code>async</code> / <code>await</code>关键字就可以在异步代码中使用普通的<code>try</code> / <code>catch</code>代码块。</p>
<div class="note">
<p><code>await</code>关键字只在async函数内有效。如果你在async函数体之外使用它,就会抛出语法错误 {{jsxref("SyntaxError")}} 。</p>
</div>
<div class="note">
<p><code>async</code>/<code>await</code>的目的为了简化使用基于promise的API时所需的语法。<code>async</code>/<code>await</code>的行为就好像搭配使用了生成器和promise。</p>
</div>
<p>async函数一定会返回一个promise对象。如果一个async函数的返回值看起来不是promise,那么它将会被隐式地包装在一个promise中。</p>
<p>例如,如下代码:</p>
<pre class="syntaxbox">async function <var>foo</var>() {
<var>return 1</var>
}
</pre>
<p>等价于:</p>
<pre class="syntaxbox">function <var>foo</var>() {
<var>return Promise.resolve(1)</var>
}
</pre>
<p>async函数的函数体可以被看作是由0个或者多个await表达式分割开来的。从第一行代码直到(并包括)第一个await表达式(如果有的话)都是同步运行的。这样的话,一个不含await表达式的async函数是会同步运行的。然而,如果函数体内有一个await表达式,async函数就一定会异步执行。</p>
<p>例如:</p>
<pre class="syntaxbox">async function <var>foo</var>() {
<var>await 1</var>
}
</pre>
<p>等价于</p>
<pre class="syntaxbox">function <var>foo</var>() {
<var>return Promise.resolve(1).then(() => undefined)</var>
}
</pre>
<p>在await表达式之后的代码可以被认为是存在在链式调用的then回调中,多个await表达式都将加入链式调用的then回调中,返回值将作为最后一个then回调的返回值。</p>
<p>在接下来的例子中,我们将使用await执行两次promise,整个<code>foo</code>函数的执行将会被分为三个阶段。</p>
<ol>
<li><code>foo</code>函数的第一行将会同步执行,await将会等待promise的结束。然后暂停通过<code>foo</code>的进程,并将控制权交还给调用<code>foo</code>的函数。</li>
<li>一段时间后,当第一个promise完结的时候,控制权将重新回到foo函数内。示例中将会将<code>1</code>(promise状态为fulfilled)作为结果返回给await表达式的左边即<code>result1</code>。接下来函数会继续进行,到达第二个await区域,此时<code>foo</code>函数的进程将再次被暂停。</li>
<li>一段时间后,同样当第二个promise完结的时候,<code>result2</code>将被赋值为<code>2</code>,之后函数将会正常同步执行,将默认返回<code>undefined</code> 。</li>
</ol>
<pre>async function <var>foo</var>() {
const result1 = <var>await new Promise((resolve) => setTimeout(() => resolve('1')))
</var> const result2 = <var>await new Promise((resolve) => setTimeout(() => resolve('2')))</var>
}
foo()</pre>
<p>注意:promise链不是一次就构建好的,相反,promise链是分阶段构造的,因此在处理异步函数时必须注意对错误函数的处理。</p>
<p>例如,在下面的代码中,在promise链上配置了<code>.catch</code>处理程序,将抛出未处理的promise错误。这是因为<code>p2</code>返回的结果不会被await处理。</p>
<pre>async function <var>foo</var>() {
const p1 = <var>new Promise((resolve) => setTimeout(() => resolve('1'), 1000))
</var> const p2 = <var>new Promise((_,reject) => setTimeout(() => reject('2'), 500))
const results = [await p1, await p2] // 不推荐使用这种方式,请使用 Promise.all或者Promise.allSettled </var>
}
foo().catch(() => {}) // 捕捉所有的错误...</pre>
<h2 id="示例">示例</h2>
<h3 id="简单例子">简单例子</h3>
<pre class="brush: js">var resolveAfter2Seconds = function() {
console.log("starting slow promise");
return new Promise(resolve => {
setTimeout(function() {
resolve("slow");
console.log("slow promise is done");
}, 2000);
});
};
var resolveAfter1Second = function() {
console.log("starting fast promise");
return new Promise(resolve => {
setTimeout(function() {
resolve("fast");
console.log("fast promise is done");
}, 1000);
});
};
var sequentialStart = async function() {
console.log('==SEQUENTIAL START==');
// 1. Execution gets here almost instantly
const slow = await resolveAfter2Seconds();
console.log(slow); // 2. this runs 2 seconds after 1.
const fast = await resolveAfter1Second();
console.log(fast); // 3. this runs 3 seconds after 1.
}
var concurrentStart = async function() {
console.log('==CONCURRENT START with await==');
const slow = resolveAfter2Seconds(); // starts timer immediately
const fast = resolveAfter1Second(); // starts timer immediately
// 1. Execution gets here almost instantly
console.log(await slow); // 2. this runs 2 seconds after 1.
console.log(await fast); // 3. this runs 2 seconds after 1., immediately after 2., since fast is already resolved
}
var concurrentPromise = function() {
console.log('==CONCURRENT START with Promise.all==');
return Promise.all([resolveAfter2Seconds(), resolveAfter1Second()]).then((messages) => {
console.log(messages[0]); // slow
console.log(messages[1]); // fast
});
}
var parallel = async function() {
console.log('==PARALLEL with await Promise.all==');
// Start 2 "jobs" in parallel and wait for both of them to complete
await Promise.all([
(async()=>console.log(await resolveAfter2Seconds()))(),
(async()=>console.log(await resolveAfter1Second()))()
]);
}
// This function does not handle errors. See warning below!
var parallelPromise = function() {
console.log('==PARALLEL with Promise.then==');
resolveAfter2Seconds().then((message)=>console.log(message));
resolveAfter1Second().then((message)=>console.log(message));
}
sequentialStart(); // after 2 seconds, logs "slow", then after 1 more second, "fast"
// wait above to finish
setTimeout(concurrentStart, 4000); // after 2 seconds, logs "slow" and then "fast"
// wait again
setTimeout(concurrentPromise, 7000); // same as concurrentStart
// wait again
setTimeout(parallel, 10000); // truly parallel: after 1 second, logs "fast", then after 1 more second, "slow"
// wait again
setTimeout(parallelPromise, 13000); // same as parallel
</pre>
<div class="note">
<h4 id="await_and_parallelism并行"><code>await</code> and parallelism(并行)</h4>
<p>在<code>sequentialStart</code>中,程序在第一个<code>await</code>停留了2秒,然后又在第二个<code>await</code>停留了1秒。直到第一个计时器结束后,第二个计时器才被创建。程序需要3秒执行完毕。</p>
<p><br>
在 <code>concurrentStart</code>中,两个计时器被同时创建,然后执行<code>await</code>。这两个计时器同时运行,这意味着程序完成运行只需要2秒,而不是3秒,即最慢的计时器的时间。</p>
<p>但是 <code>await </code>仍旧是顺序执行的,第二个 <code>await</code> 还是得等待第一个执行完。在这个例子中,这使得先运行结束的输出出现在最慢的输出之后。</p>
<p>如果你希望并行执行两个或更多的任务,你必须像在<code>parallel</code>中一样使用<code>await Promise.all([job1(), job2()])</code>。</p>
</div>
<div class="warning">
<h4 id="asyncawait和Promisethen对比以及错误处理"><code>async</code>/<code>await和</code>Promise#then对比以及错误处理</h4>
<p>大多数async函数也可以使用Promises编写。但是,在错误处理方面,async函数更容易捕获异常错误</p>
<p>上面例子中的<code>concurrentStart</code>函数和<code>concurrentPromise</code>函数在功能上都是等效的。在<code>concurrentStart</code>函数中,如果任一<code>await</code>ed调用失败,它将自动捕获异常,async函数执行中断,并通过隐式返回Promise将错误传递给调用者。</p>
<p>在Promise例子中这种情况同样会发生,该函数必须负责返回一个捕获函数完成的<code>Promise</code>。在<code>concurrentPromise</code>函数中,这意味着它从<code>Promise.all([]).then()</code>返回一个Promise。事实上,在此示例的先前版本忘记了这样做!</p>
<p>但是,async函数仍有可能然可能错误地忽略错误。<br>
以<code>parallel</code> async函数为例。 如果它没有等待<code>await</code>(或返回)<code>Promise.all([])</code>调用的结果,则不会传播任何错误。<br>
虽然<code>parallelPromise</code>函数示例看起来很简单,但它根本不会处理错误! 这样做需要一个类似于<code>return </code><code>Promise.all([])</code>处理方式。</p>
</div>
<h3 id="使用async函数重写_promise_链">使用async函数重写 promise 链</h3>
<p>返回 {{jsxref("Promise")}}的 API 将会产生一个 promise 链,它将函数肢解成许多部分。例如下面的代码:</p>
<pre class="brush: js">function getProcessedData(url) {
return downloadData(url) // 返回一个 promise 对象
.catch(e => {
return downloadFallbackData(url) // 返回一个 promise 对象
})
.then(v => {
return processDataInWorker(v); // 返回一个 promise 对象
});
}</pre>
<p>可以重写为单个async函数:</p>
<pre class="brush: js">async function getProcessedData(url) {
let v;
try {
v = await downloadData(url);
} catch (e) {
v = await downloadFallbackData(url);
}
return processDataInWorker(v);
}
</pre>
<p>注意,在上述示例中,<code>return</code> 语句中没有 <code>await</code> 操作符,因为 <code>async function</code> 的返回值将被隐式地传递给 <code>{{jsxref("Promise.resolve")}}</code>。</p>
<div class="blockIndicator note">
<p><strong><code>return await promiseValue;</code> 与 <code>return promiseValue;的比较</code></strong></p>
<p>返回值<code>隐式的传递给</code>{{jsxref("Promise.resolve")}},并不意味着<code>return await promiseValue;和return promiseValue;</code>在功能上相同。</p>
<p>看下下面重写的上面代码,在<code>processDataInWorker</code>抛出异常时返回了null:</p>
<pre><code>async function getProcessedData(url) {
let v;
try {
v = await downloadData(url);
} catch(e) {
v = await downloadFallbackData(url);
}
try {
return await processDataInWorker(v); // 注意 `return await` 和单独 `return` 的比较
} catch (e) {
return null;
}
}</code></pre>
<p>简单地写上<code>return processDataInworker(v);将导致在processDataInWorker(v)</code>出错时function返回值为{{jsxref("Promise")}}<code>而不是</code>返回null。<code>return foo;</code>和<code>return await foo;</code>,有一些细微的差异:<code>return foo;</code>不管<code>foo</code>是promise还是rejects都将会直接返回<code>foo。相反地,</code>如果<code>foo</code>是一个{{jsxref("Promise")}},<code>return await foo;</code>将等待<code>foo</code>执行(resolve)或拒绝(reject),如果是拒绝,将会在返回前抛出异常。</p>
</div>
<h2 id="规范">规范</h2>
<table class="standard-table">
<thead>
<tr>
<th scope="col">Specification</th>
<th scope="col">Status</th>
<th scope="col">Comment</th>
</tr>
</thead>
<tbody>
<tr>
<td>{{SpecName('ESDraft', '#sec-async-function-definitions', 'async function')}}</td>
<td>{{Spec2('ESDraft')}}</td>
<td>初始定义于ES2017.</td>
</tr>
<tr>
<td>{{SpecName('ES8', '#sec-async-function-definitions', 'async function')}}</td>
<td>{{Spec2('ES8')}}</td>
<td></td>
</tr>
</tbody>
</table>
<h2 id="浏览器兼容性">浏览器兼容性</h2>
<p>{{Compat("javascript.statements.async_function")}}</p>
<h2 id="参见">参见</h2>
<ul>
<li>{{jsxref("Operators/async_function", "async function expression")}}</li>
<li>{{jsxref("AsyncFunction")}} object</li>
<li>{{jsxref("Operators/await", "await")}}</li>
<li><a href="http://innolitics.com/10x/javascript-decorators-for-promise-returning-functions/">"Decorating Async Javascript Functions" on "innolitics.com"</a></li>
</ul>
|