异步函数是ES2017 中的一个特性,它通过使用promise(一种特殊形式的异步代码)和await
关键字使异步代码看起来同步。还要注意在下面的代码示例中,关键字async
前面的function
关键字表示 async/await 函数。如果await
不在以关键字为前缀的函数中,关键字将无法工作async
。由于目前没有例外,这意味着没有顶级等待将起作用(顶级等待意味着在任何函数之外等待)。尽管有顶级await
.
ES2017 于 2017 年 6 月 27 日被批准(即最终确定)为 JavaScript 的标准。 Async await 可能已经在您的浏览器中工作,但如果没有,您仍然可以使用babel或traceur等 javascript 转译器使用该功能。Chrome 55 完全支持异步功能。因此,如果您有较新的浏览器,您可以试试下面的代码。
有关浏览器兼容性,请参阅kangax 的 es2017 兼容性表。
这是一个调用的示例异步等待函数doAsync
,它需要三个一秒的暂停,并在每次暂停后从开始时间打印时间差:
function timeoutPromise (time) {
return new Promise(function (resolve) {
setTimeout(function () {
resolve(Date.now());
}, time)
})
}
function doSomethingAsync () {
return timeoutPromise(1000);
}
async function doAsync () {
var start = Date.now(), time;
console.log(0);
time = await doSomethingAsync();
console.log(time - start);
time = await doSomethingAsync();
console.log(time - start);
time = await doSomethingAsync();
console.log(time - start);
}
doAsync();
当 await 关键字放在Promise值之前(在这种情况下,Promise值是函数 doSomethingAsync 返回的值),await 关键字将暂停函数调用的执行,但不会暂停任何其他函数,它将继续执行其他代码直到Promise解决。在 promise 解析之后,它将解包 promise 的值,您可以将 await 和 promise 表达式视为现在被解包的值替换。
因此,由于 await 只是暂停等待然后在执行该行的其余部分之前解包一个值,因此您可以在 for 循环和内部函数调用中使用它,如下例所示,该示例收集在数组中等待的时间差并打印出数组。
function timeoutPromise (time) {
return new Promise(function (resolve) {
setTimeout(function () {
resolve(Date.now());
}, time)
})
}
function doSomethingAsync () {
return timeoutPromise(1000);
}
// this calls each promise returning function one after the other
async function doAsync () {
var response = [];
var start = Date.now();
// each index is a promise returning function
var promiseFuncs= [doSomethingAsync, doSomethingAsync, doSomethingAsync];
for(var i = 0; i < promiseFuncs.length; ++i) {
var promiseFunc = promiseFuncs[i];
response.push(await promiseFunc() - start);
console.log(response);
}
// do something with response which is an array of values that were from resolved promises.
return response
}
doAsync().then(function (response) {
console.log(response)
})
异步函数本身返回一个Promise,因此您可以将其用作带有链接的Promise,就像我在上面所做的那样或在另一个异步等待函数中使用。
上面的函数会在发送另一个请求之前等待每个响应,如果你想同时发送请求,你可以使用Promise.all。
// no change
function timeoutPromise (time) {
return new Promise(function (resolve) {
setTimeout(function () {
resolve(Date.now());
}, time)
})
}
// no change
function doSomethingAsync () {
return timeoutPromise(1000);
}
// this function calls the async promise returning functions all at around the same time
async function doAsync () {
var start = Date.now();
// we are now using promise all to await all promises to settle
var responses = await Promise.all([doSomethingAsync(), doSomethingAsync(), doSomethingAsync()]);
return responses.map(x=>x-start);
}
// no change
doAsync().then(function (response) {
console.log(response)
})
如果Promise可能拒绝,您可以将其包装在 try catch 中或跳过 try catch 并让错误传播到 async/await 函数的 catch 调用。你应该小心不要让 promise 错误得不到处理,尤其是在 Node.js 中。下面是一些展示错误如何工作的示例。
function timeoutReject (time) {
return new Promise(function (resolve, reject) {
setTimeout(function () {
reject(new Error("OOPS well you got an error at TIMESTAMP: " + Date.now()));
}, time)
})
}
function doErrorAsync () {
return timeoutReject(1000);
}
var log = (...args)=>console.log(...args);
var logErr = (...args)=>console.error(...args);
async function unpropogatedError () {
// promise is not awaited or returned so it does not propogate the error
doErrorAsync();
return "finished unpropogatedError successfully";
}
unpropogatedError().then(log).catch(logErr)
async function handledError () {
var start = Date.now();
try {
console.log((await doErrorAsync()) - start);
console.log("past error");
} catch (e) {
console.log("in catch we handled the error");
}
return "finished handledError successfully";
}
handledError().then(log).catch(logErr)
// example of how error propogates to chained catch method
async function propogatedError () {
var start = Date.now();
var time = await doErrorAsync() - start;
console.log(time - start);
return "finished propogatedError successfully";
}
// this is what prints propogatedError's error.
propogatedError().then(log).catch(logErr)
如果你去这里你可以看到即将到来的 ECMAScript 版本的完成提案。
可以仅与 ES2015 (ES6) 一起使用的替代方法是使用包装生成器函数的特殊函数。生成器函数有一个 yield 关键字,可用于复制带有周围函数的 await 关键字。yield 关键字和生成器函数具有更通用的用途,可以做更多的事情,而不是 async await 函数所做的事情。如果你想要一个可用于复制异步等待的生成器函数包装器,我会查看co.js。顺便说一下,co 的函数很像 async await 函数返回一个 promise。老实说,虽然在这一点上,生成器函数和异步函数的浏览器兼容性大致相同,所以如果你只想要异步等待功能,你应该使用没有 co.js 的异步函数。
(我建议只使用 async/await,它在支持上述删除线的大多数环境中得到了广泛支持。)
现在,除 IE 之外的所有主流浏览器(Chrome、Safari 和 Edge)中的 Async 功能(截至 2017 年)的浏览器支持实际上都非常好。