Async functions,in ES2017 的一项功能,通过使用 promises(一种特殊形式的异步代码)和 await 关键字使异步代码看起来同步。还要注意在下面的代码示例中,function 关键字前面的关键字 async 表示异步/等待函数。如果没有在以 async 关键字为前缀的函数中,await 关键字将不起作用。由于目前没有例外,这意味着没有*等待将起作用(*等待意味着任何函数之外的等待)。虽然有一个proposal for top-level await。
ES2017 在 2017 年 6 月 27 日被批准(即最终确定)为 JavaScript 的标准。异步等待可能已经在您的浏览器中运行,但如果不是,您仍然可以使用 JavaScript 转译器(如 babel 或 @987654326)来使用该功能@。 Chrome 55 完全支持异步功能。所以如果你有更新的浏览器,你可以试试下面的代码。
有关浏览器兼容性,请参阅kangax's es2017 compatibility table。
这是一个名为 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.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 调用。你应该小心不要留下未处理的承诺错误,尤其是在 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)
如果您去here,您可以看到即将推出的 ECMAScript 版本的已完成提案。
可以仅用于 ES2015 (ES6) 的替代方法是使用包装生成器函数的特殊函数。生成器函数有一个 yield 关键字,可以用来复制 await 关键字和周围的函数。 yield 关键字和生成器函数更通用,可以做更多的事情,而不是 async await 函数所做的事情。如果您想要一个可用于复制异步等待的生成器函数包装器,我会查看co.js。顺便说一句,co 的函数很像异步等待函数返回一个承诺。老实说,虽然此时浏览器兼容性对于生成器函数和异步函数大致相同,所以如果你只想要异步等待功能,你应该使用不带 co.js 的异步函数。
(我建议只使用 async/await,它在大多数支持上述删除线的环境中都得到了广泛支持。)
除了 IE 之外,当前所有主流浏览器(Chrome、Safari 和 Edge)中的异步功能(截至 2017 年)的浏览器支持实际上都非常好。