From 33058f2b292b3a581333bdfb21b8f671898c5060 Mon Sep 17 00:00:00 2001 From: Peter Bengtsson Date: Tue, 8 Dec 2020 14:40:17 -0500 Subject: initial commit --- .../reference/statements/async_function/index.html | 327 +++++++++++++++++++++ 1 file changed, 327 insertions(+) create mode 100644 files/zh-cn/web/javascript/reference/statements/async_function/index.html (limited to 'files/zh-cn/web/javascript/reference/statements/async_function') diff --git a/files/zh-cn/web/javascript/reference/statements/async_function/index.html b/files/zh-cn/web/javascript/reference/statements/async_function/index.html new file mode 100644 index 0000000000..cb6f595ff9 --- /dev/null +++ b/files/zh-cn/web/javascript/reference/statements/async_function/index.html @@ -0,0 +1,327 @@ +--- +title: async函数 +slug: Web/JavaScript/Reference/Statements/async_function +tags: + - JavaScript + - 函数 + - 声明 + - 异步函数 + - 语言特性 +translation_of: Web/JavaScript/Reference/Statements/async_function +--- +
{{jsSidebar("Statements")}}
+ +

async函数是使用async关键字声明的函数。 async函数是{{jsxref("Global_Objects/AsyncFunction","AsyncFunction")}}构造函数的实例, 并且其中允许使用await关键字。asyncawait关键字让我们可以用一种更简洁的方式写出基于{{jsxref("Promise")}}的异步行为,而无需刻意地链式调用promise

+ +
+

async函数还可以被{{jsxref("Operators/async_function", "作为表达式", "", 1)}}来定义。

+
+ +
{{EmbedInteractiveExample("pages/js/statement-async.html", "taller")}}
+ + + +

语法

+ +
async function name([param[, param[, ... param]]]) {
+    statements 
+}
+
+ +

参数

+ +
+
name
+
函数名称。
+
+ +
+
param
+
要传递给函数的参数的名称。
+
+ +
+
statements
+
包含函数主体的表达式。可以使用await机制。
+
+

返回值

+
+
一个{{jsxref("Promise")}},这个promise要么会通过一个由async函数返回的值被解决,要么会通过一个从async函数中抛出的(或其中没有被捕获到的)异常被拒绝。
+
+ +

描述

+ +

async函数可能包含0个或者多个{{jsxref("Operators/await", "await")}}表达式。await表达式会暂停整个async函数的执行进程并出让其控制权,只有当其等待的基于promise的异步操作被兑现或被拒绝之后才会恢复进程。promise的解决值会被当作该await表达式的返回值。使用async / await关键字就可以在异步代码中使用普通的try / catch代码块。

+ +
+

await关键字只在async函数内有效。如果你在async函数体之外使用它,就会抛出语法错误 {{jsxref("SyntaxError")}} 。

+
+ +
+

async/await的目的为了简化使用基于promise的API时所需的语法。async/await的行为就好像搭配使用了生成器和promise。

+
+ +

async函数一定会返回一个promise对象。如果一个async函数的返回值看起来不是promise,那么它将会被隐式地包装在一个promise中。

+ +

例如,如下代码:

+ +
async function foo() {
+   return 1
+}
+
+ +

等价于:

+ +
function foo() {
+   return Promise.resolve(1)
+}
+
+ +

async函数的函数体可以被看作是由0个或者多个await表达式分割开来的。从第一行代码直到(并包括)第一个await表达式(如果有的话)都是同步运行的。这样的话,一个不含await表达式的async函数是会同步运行的。然而,如果函数体内有一个await表达式,async函数就一定会异步执行。

+ +

例如:

+ +
async function foo() {
+   await 1
+}
+
+ +

等价于

+ +
function foo() {
+   return Promise.resolve(1).then(() => undefined)
+}
+
+ +

在await表达式之后的代码可以被认为是存在在链式调用的then回调中,多个await表达式都将加入链式调用的then回调中,返回值将作为最后一个then回调的返回值。

+ +

在接下来的例子中,我们将使用await执行两次promise,整个foo函数的执行将会被分为三个阶段。

+ +
    +
  1. foo函数的第一行将会同步执行,await将会等待promise的结束。然后暂停通过foo的进程,并将控制权交还给调用foo的函数。
  2. +
  3. 一段时间后,当第一个promise完结的时候,控制权将重新回到foo函数内。示例中将会将1(promise状态为fulfilled)作为结果返回给await表达式的左边即result1。接下来函数会继续进行,到达第二个await区域,此时foo函数的进程将再次被暂停。
  4. +
  5. 一段时间后,同样当第二个promise完结的时候,result2将被赋值为2,之后函数将会正常同步执行,将默认返回undefined 。
  6. +
+ +
async function foo() {
+   const result1 = await new Promise((resolve) => setTimeout(() => resolve('1')))
+   const result2 = await new Promise((resolve) => setTimeout(() => resolve('2')))
+}
+foo()
+ +

注意:promise链不是一次就构建好的,相反,promise链是分阶段构造的,因此在处理异步函数时必须注意对错误函数的处理。

+ +

例如,在下面的代码中,在promise链上配置了.catch处理程序,将抛出未处理的promise错误。这是因为p2返回的结果不会被await处理。

+ +
async function foo() {
+   const p1 = new Promise((resolve) => setTimeout(() => resolve('1'), 1000))
+   const p2 = new Promise((_,reject) => setTimeout(() => reject('2'), 500))
+   const results = [await p1, await p2] // 不推荐使用这种方式,请使用 Promise.all或者Promise.allSettled 
+}
+foo().catch(() => {}) // 捕捉所有的错误...
+ + + +

示例

+ +

简单例子

+ +
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
+
+ +
+

await and parallelism(并行)

+ +

sequentialStart中,程序在第一个await停留了2秒,然后又在第二个await停留了1秒。直到第一个计时器结束后,第二个计时器才被创建。程序需要3秒执行完毕。

+ +


+ 在 concurrentStart中,两个计时器被同时创建,然后执行await。这两个计时器同时运行,这意味着程序完成运行只需要2秒,而不是3秒,即最慢的计时器的时间。

+ +

但是 await 仍旧是顺序执行的,第二个 await 还是得等待第一个执行完。在这个例子中,这使得先运行结束的输出出现在最慢的输出之后。

+ +

如果你希望并行执行两个或更多的任务,你必须像在parallel中一样使用await Promise.all([job1(), job2()])

+
+ +
+

async/await和Promise#then对比以及错误处理

+ +

大多数async函数也可以使用Promises编写。但是,在错误处理方面,async函数更容易捕获异常错误

+ +

上面例子中的concurrentStart函数和concurrentPromise函数在功能上都是等效的。在concurrentStart函数中,如果任一awaited调用失败,它将自动捕获异常,async函数执行中断,并通过隐式返回Promise将错误传递给调用者。

+ +

在Promise例子中这种情况同样会发生,该函数必须负责返回一个捕获函数完成的Promise。在concurrentPromise函数中,这意味着它从Promise.all([]).then()返回一个Promise。事实上,在此示例的先前版本忘记了这样做!

+ +

但是,async函数仍有可能然可能错误地忽略错误。
+ 以parallel async函数为例。 如果它没有等待await(或返回)Promise.all([])调用的结果,则不会传播任何错误。
+ 虽然parallelPromise函数示例看起来很简单,但它根本不会处理错误! 这样做需要一个类似于return Promise.all([])处理方式。

+
+ +

使用async函数重写 promise 链

+ +

返回 {{jsxref("Promise")}}的 API 将会产生一个 promise 链,它将函数肢解成许多部分。例如下面的代码:

+ +
function getProcessedData(url) {
+  return downloadData(url) // 返回一个 promise 对象
+    .catch(e => {
+      return downloadFallbackData(url)  // 返回一个 promise 对象
+    })
+    .then(v => {
+      return processDataInWorker(v); // 返回一个 promise 对象
+    });
+}
+ +

可以重写为单个async函数:

+ +
async function getProcessedData(url) {
+  let v;
+  try {
+    v = await downloadData(url);
+  } catch (e) {
+    v = await downloadFallbackData(url);
+  }
+  return processDataInWorker(v);
+}
+
+ +

注意,在上述示例中,return 语句中没有 await 操作符,因为 async function 的返回值将被隐式地传递给 {{jsxref("Promise.resolve")}}

+ +
+

return await promiseValue; 与 return promiseValue;的比较

+ +

返回值隐式的传递给{{jsxref("Promise.resolve")}},并不意味着return await promiseValue;和return promiseValue;在功能上相同。

+ +

看下下面重写的上面代码,在processDataInWorker抛出异常时返回了null:

+ +
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;
+  }
+}
+ +

简单地写上return processDataInworker(v);将导致在processDataInWorker(v)出错时function返回值为{{jsxref("Promise")}}而不是返回null。return foo;return await foo;,有一些细微的差异:return foo;不管foo是promise还是rejects都将会直接返回foo。相反地,如果foo是一个{{jsxref("Promise")}},return await foo;将等待foo执行(resolve)或拒绝(reject),如果是拒绝,将会在返回前抛出异常。

+
+ +

规范

+ + + + + + + + + + + + + + + + + + + + + +
SpecificationStatusComment
{{SpecName('ESDraft', '#sec-async-function-definitions', 'async function')}}{{Spec2('ESDraft')}}初始定义于ES2017.
{{SpecName('ES8', '#sec-async-function-definitions', 'async function')}}{{Spec2('ES8')}}
+ +

浏览器兼容性

+ + + +

{{Compat("javascript.statements.async_function")}}

+ +

参见

+ + -- cgit v1.2.3-54-g00ecf