异步函数(ES2017中的一项功能)通过使用promise(一种特殊形式的异步代码)和await
关键字使异步代码看起来同步。还要注意在代码示例下面的关键字async
前面的function
关键字之前,该关键字表示async / await函数。如果await
没有关键字预先固定的功能,该关键字将无法工作async
。由于当前没有例外,这意味着没有顶级等待将起作用(顶级等待意味着任何功能之外的等待)。尽管有关于高层await
的建议。
ES2017已于2017年6月27日批准(即最终确定)为JavaScript的标准。异步等待可能已在您的浏览器中运行,但是如果没有,您仍然可以使用像babel或traceur这样的javascript编译器来使用该功能。Chrome 55完全支持异步功能。因此,如果您使用的是更新的浏览器,则可以尝试以下代码。
请参阅kangax的es2017兼容性表以了解浏览器兼容性。
这是一个示例async await函数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关键字将暂停函数调用的执行,但不会暂停任何其他函数,并且它将继续执行其他代码,直到承诺解决为止。承诺解决后,它将解包承诺的值,您可以认为等待和承诺表达式现在已被该未包装的值替换。
因此,由于await只是暂停等待,然后在执行其余的行之前取消包装值,因此您可以在循环和内部函数调用中使用它,如以下示例所示,该示例收集数组中等待的时间差并打印出数组。
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,因此您可以像在上面或另一个异步await函数中所做的那样,将它用作与链接的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调用。您应该注意不要遗漏诺言错误,尤其是在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关键字和generator函数具有更多通用性,并且可以完成许多功能,而不仅仅是async await函数。如果你想,可以用来复制异步发电机的功能封装等待我想看看co.js。顺便说一下,co的函数很像异步await函数返回promise。老实说,尽管此时浏览器对生成器功能和异步功能的兼容性几乎相同,所以如果您只想要异步等待功能,则应使用不带co.js的异步功能。
现在,除IE之外,当前所有主流浏览器(Chrome,Safari和Edge)中的异步功能(截至2017年)实际上对浏览器的支持都不错。