async函数
async函数是使用async关键字声明的函数。async函数是AsyncFunction构造函数的实例。并且其中允许使用await关键字。async和await关键字让我们可以用一种更简洁的方式写出基于Promise的异步行为,而无需刻意地链式调用promise。
async函数还可以被作为表达式来定义。
function resolveAfter2Seconds() {
return new Promise(resolve => {
setTimeout(() => {
resolve('resolved');
},1000);
});
}
async function asyncCall() {
console.log('calling');
const result = await resolveAfter2Seconds();
console.log(result); // resolved
}
asyncCall();
语法
async function afun(param) {
statements
}
async function afun(param0, param1) {
statements
}
async function afun(param0, param1, /*...,*/, paramN) {
statemnts
}
参数
- name 函数名称w
- param 要传递给函数的参数的名称。
- statements 包含函数主体的表达式。可以使用
await机制。 - 返回值 一个
promise,这个promise要么会通过一个由async函数返回的值被解决。要么会通过一个从async函数中抛出的(或者其中没有被捕获到的)异常被拒绝。
描述
async函数可能包含0个或者多个await表达式。await表达式会暂停整个async函数的执行进程并出让其控制权,只有当其等待的基于promise的异步操作被兑现或被拒绝之后才会恢复进程。promise的解决值会被当作该await表达式的返回值。使用async/await关键字就可以在异步代码中使用普通的try/catch代码块。
await关键字只在async函数内有效。如果你在async函数体之外使用它。就会抛出语法错误
SyntaxError。
async/await的目的为了简化使用基于Promise的Api时所需的语法。async/await的行为就好像搭配使用了生成器和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(() => undeinfed);
}
在await表达式之后的代码可以被认为是存在在链式调用的then回调中,多个await表达式都将加入链式调用的then回调中,返回值将作为最后一个then回调的返回值。
在接下来的例子中,我们将使用await执行两次promise,整个foo函数的执行将会被分为三个阶段。
foo函数的第一行将会同步执行,await将会等待promise的结束。然后暂停通过foo的进程,并将控制权交还给调用foo的函数。- 一段时间后,当第一个promise完结的时候,控制权将重新回到foo函数内,示例中将会将1(promise状态为fulfilled)作为结果返回给await表达式的左边即
result1。接下来函数会继续进行,到达第二个await区域,此时foo函数的进程将再次被暂停。 - 一段时间后,同样当第二个promise完结的时候,
result2将会被赋值为2,之后函数将会正常同步执行,将默认返回undefined。
async function foo() {
const result1 = await new Promise((resolve) =>
setTimeout(() => resolve("1"))
);
// 等待result1返回继续往下执行....
const result2 = await new Promise((resolve) =>
setTimout(() => resolve("2"))
);
}
foo();
注意:promise链不是一次就构建好的,相反,promise链是分阶段构造的,因此在处理异步函数时必须注意对错误函数的处理。
例如,在下面代码中,即使在promise链中进一步配置了.catch方法处理,也会抛出一个未处理的promsie被拒绝的错误。这是因为p2直到控制从p1返回后才会连接到promsie链。
async function foo() {
const p1 = new Promise((resolve) => setTimeout(() => resolve("1"), 1000));
const p2 = new Promise((_,reject) => setTimeout(() => reject("2"), 500));
const result = [await p1, await p2]; // 不推荐使用这种方式,请使用 Promise.all 或者 Promise.allSettled
}
foo().catch(() => {}); // 捕获所有的错误...
示例
function resolveAfter2Seconds() {
console.log("starting slow promise");
return new Promise((resolve) => {
setTimeout(() => {
resolve("slow");
console.log("slow promise is done");
}, 2000);
})
}
function resolveAfter1Second() {
console.log("starting fast promise");
return new Promise((resolve) => {
setTimeout(() => {
resolve("fast");
console.log("fast promise is done");
}, 1000);
})
}
async function sequentialStart() {
console.log("==SEQUENTIAL START==");
// 1.执行几乎是即时的
const slow = await resolveAfter2Seconds();
console.log(slow); // 它在2秒后运行。
const fast = await resolveAfter1Second();
console.log(fast); // 它在3秒后运行 因为它要等上面2秒的执行完才可以执行
}
sequentialStart();
/*
执行顺序
==SEQUENTIAL START==
starting slow promise
slow promise is done
slow
starting fast promise
fast promise is done
fast
*/
async function concurrentStart() {
console.log("==CONCURRENT START with await ==");
const slow = resolveAfter2Seconds(); // 立即启动计时器
const fast = resolveAfter1Second(); // 立即启动计时器
// 1.行几乎是即时的
console.log(await slow);
console.log(await fast);
}
concurrentStart();
/*
执行顺序
==CONCURRENT START with await ==
starting slow promise
starting fast promise
fast promise is done
slow promise is done
slow
fast
*/
function concurrentPromise() {
console.log("==CONCURRENT START with Promise.all==");
return Promise.all([resolveAfter2Seconds(), resolveAfter1Second]).then(
(message) => {
console.log(message[0]); // slow
console.log(message[1]); // fast
}
)
}
concurrentPromise(); // 执行顺序除了第一行其他同上
async function parallel() {
console.log("==PARALLEL with await Promise.all==");
// 将两个“任务”并行执行,并等待它们都完成
await Promise.all([
(async () => console.log(await resolveAfter2Seconds()))(),
(async () => console.log(await resolveAfter1Second()))(),
]);
}
parallel();
/*
执行顺序
==PARALLEL with await Promise.all==
starting slow promise
starting fast promise
fast promise is done
fast
slow promise is done
slow
*/
在 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 链
返回 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 的返回值将被隐式地传递给 Promise.resolve。
返回值 隐式的传递给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 返回值为Promise而不是返回 null。return foo; 和 return await foo;,有一些细微的差异:return foo;不管foo是 promise 还是 rejects 都将会直接返回foo。相反地,如果foo是一个Promise,return await foo;将等待foo执行 (resolve) 或拒绝 (reject),如果是拒绝,将会在返回前抛出异常。