现在以下几种async函数的用法都是可行的。
Async函数声明:
async function foo(){}Async函数表达式:
const foo = async function(){}Async方法定义:
let obj = {async foo(){}}Async箭头函数:
const foo = async () => {}Async函数通常返回的是 Promise:
async function asyncFunc() {
return 123;
}
asyncFunc()
.then(x => console.log(x));123
async function asyncFunc() {
throw new Error('Problem!');
}
asyncFunc()
.catch(err => console.log(err));Error: Problem!
只有操作符await可以放在 Async 函数里用来处理返回的 Promise 对象结果。所以await的处理结果会随着 Promise 的状态不同而不同。
一个简单的异步处理:
async function asyncFunc() {
const result = await otherAsyncFunc();
console.log(result);
}
// 等价于:
function asyncFunc() {
return otherAsyncFunc()
.then(result => {
console.log(result);
});
}顺序处理多个异步结果:
async function asyncFunc() {
const result1 = await otherAsyncFunc1();
console.log(result1);
const result2 = await otherAsyncFunc2();
console.log(result2);
}
// 等价于:
function asyncFunc() {
return otherAsyncFunc1()
.then(result1 => {
console.log(result1);
return otherAsyncFunc2();
})
.then(result2 => {
console.log(result2);
});
}平行处理多个异步结果:
async function asyncFunc() {
const [result1, result2] = await Promise.all([
otherAsyncFunc1(),
otherAsyncFunc2(),
]);
console.log(result1, result2);
}
// 等价于:
function asyncFunc() {
return Promise.all([
otherAsyncFunc1(),
otherAsyncFunc2(),
])
.then([result1, result2] => {
console.log(result1, result2);
});
}处理错误异常:
async function asyncFunc() {
try {
await otherAsyncFunc();
} catch (err) {
console.error(err);
}
}
// 等价于:
function asyncFunc() {
return otherAsyncFunc()
.catch(err => {
console.error(err);
});
}在解释async函数之前,我想通过把Promises和generator结合起来,用看起来像同步代码的方式去模拟一下异步。
对于处理获取一次性结果的异步函数,Promises是目前最流行的方式。下面是一个使用fetch方法获取文件的示例:
function fetchJson(url) {
return fetch(url)
.then(request => request.text())
.then(text => {
return JSON.parse(text);
})
.catch(error => {
console.log(`ERROR: ${error.stack}`);
});
}
fetchJson('http://example.com/some_file.json')
.then(obj => console.log(obj));co是一个基于Promises和generator的实现,也能够让你以书写同步代码的方式来实现上面的示例。
const fetchJson = co.wrap(function* (url) {
try {
let request = yield fetch(url);
let text = yield request.text();
return JSON.parse(text);
}
catch (error) {
console.log(`ERROR: ${error.stack}`);
}
});co在generator回调函数中每次检测到一个带有yield操作符的方法,就会产生一个Promise对象,co会先暂停回调代码的执行,直到Promise对象的状态发生变化再继续执行。无论Promise的状态为resolved或者rejected,yield都会将相应的结果值返回。
详细说明一下async函数的执行过程:
async函数在开始执行的时候,通常都是返回一个Promise对象- 函数体被执行之后,你可以使用
return或者throw直接完成执行过程。也可以使用await暂时完成执行过程,然后根据情况再继续执行 - 最终返回一个
Promise对象 then和catch中的callback只有在当前所有代码执行完毕之后,才会被执行。从下面的输出结果可以看出函数asyncFunc的返回值等到所有代码包括循环逻辑都执行完毕之后,才最终得以被输出
async function asyncFunc() {
console.log('asyncFunc()'); // (A)
return 'abc';
}
asyncFunc().
then(x => console.log(`Resolved: ${x}`)); // (B)
console.log('main');
for(let i=0; i<5; i++){
console.log(i)
}asyncFunc()
main
0
1
2
3
4
Resolved: abc
使用return去Resolve一个async函数状态,是一种很标准的操作方式。这意味着你可以:
- 直接返回一个非
Promise对象类型的值,作为Resolve状态的参数值 - 返回的
Promise对象代表了当前async的函数状态
使用async函数最常犯的一个错误就是忘记添加await关键字,例如下面的示例中value指向了一个Promise对象,但是忘了添加await关键字,所以输出的可能并不是你想要的结果:
async function asyncFunc() {
const value = otherAsyncFunc(); // missing `await`!
···
}await可以感知到后面跟的 Promise 异步函数是否有结果值,例如在下面的示例中,await可以确保step1()执行完之前,不会执行foo()的剩余逻辑代码:
async function foo() {
await step1(); // (A)
···
}有时候你仅仅是想触发一个异步函数计算,并不想知道它会何时完成。例如在下面示例中,我们并不关心写文件的操作何时完成,只要它们是按正确的顺序执行就可以了。最后一行的await只是为了确保关闭写文件的操作能被成功执行即可。
async function asyncFunc() {
const writer = openFile('someFile.txt');
writer.write('hello'); // don’t wait
writer.write('world'); // don’t wait
await writer.close(); // wait for file to close
}多个await异步函数是顺序执行的关系,想要它们同时执行就得使用Promise.all()了:
async function foo() {
const result1 = await asyncFunc1();
const result2 = await asyncFunc2();
}async function foo() {
const [result1, result2] = await Promise.all([
asyncFunc1(),
asyncFunc2(),
]);
}有一个需要知道的限制是,await操作符只会影响async函数的直接作用域环境。因此,你不能在一个async方法的回调函数中直接使用await。
下面的示例是根据 url 下载一些资源:
async function downloadContent(urls) {
return urls.map(url => {
// Wrong syntax!
const content = await httpGet(url);
return content;
});
}像上面这种在普通的箭头函数中使用await是无法运行的,会抛出语法错误。那我们应该怎么用,像下面这样吗?
async function downloadContent(urls) {
return urls.map(async (url) => {
const content = await httpGet(url);
return content;
});
}
function httpGet(url){
return new Promise(function(resolve, reject){
setTimeout(function(){
if(url.indexOf('a') > -1){
resolve('a.com');
}else{
resolve('b.com');
}
}, 2000)
});
}
const contents = downloadContent(['http://a.com', 'http://b.com']);
console.log(contents);
contents.then((url) => {
console.log(url);
});输出结果:
你会发现代码中有两个问题:
- 返回值是一个包含两个
Promise对象的数组,并不是我们期望的包含resolve返回值的数组 await只能暂停箭头回调函数里的httpGet(),map本身的回调函数执行完成之后,并不能影响外层的downloadContent ()也执行完成
我们用Promise.all()来修复这两个问题,把返回的 包含两个Promise对象的数组 转换成 包含两个数组元素的Promise对象,看如下示例:
async function downloadContent(urls) {
const promiseArray = urls.map(url => httpGet(url));
return await Promise.all(promiseArray);
}
function httpGet(url){
return new Promise(function(resolve, reject){
setTimeout(function(){
if(url.indexOf('a') > -1){
resolve('a.com');
}else{
resolve('b.com');
}
}, 2000)
});
}
const contents = downloadContent(['http://a.com', 'http://b.com']);
console.log(contents);
contents.then((url) => {
console.log(url);
});输出结果:
OK,现在输出结果是正确的了。但是这段代码还是有一点点低效的地方需要改进,downloadContent ()函数里首先用await展开了Promise.all()的返回结果,后面又用return包装了一次,其实我们用return直接返回Promise.all()即可:
async function downloadContent(urls) {
const promiseArray = urls.map(url => httpGet(url));
return Promise.all(promiseArray);
}我们这次换成forEach()方法来模拟输出若干文件内容。很显然,下面的示例会抛出语法错误,因为你不能在普通的箭头函数中直接使用await:
async function logContent(urls) {
urls.forEach(url => {
// Wrong syntax
const content = await httpGet(url);
console.log(content);
});
}那我把代码修改成如下:
async function logContent(urls) {
urls.forEach(async url => {
const content = await httpGet(url);
console.log(content);
});
// Not finished here
}这次代码倒是运行了,但是httpGet()方法返回resolve状态的操作是异步的,也就是说当forEach()方法已经返回之后,它的callback还并没有执行完成。修复此问题,只需要将代码做一下更改:
async function logContent(urls) {
for (const url of urls) {
const content = await httpGet(url);
console.log(content);
}
}这段示例中的httpGet()执行顺序是线性的,每一次的调用必须要等待上一次执行完毕。如果想要改成并行的执行顺序,就得用Promise.all()了:
async function logContent(urls) {
await Promise.all(urls.map(
async url => {
const content = await httpGet(url);
console.log(content);
}));
}map()方法创建了一个Promise对象数组。我们并不关心这几个Promise对象的履行结果,只要它们履行了即可。也就是loginContent()方法执行完成就可以了。在这个示例中除非把Promise.all()直接返回,否则此函数的结果只会包含若干`undefined。
async函数的基础就是Promise,所以充分理解下面的示例非常重要。尤其是在那些没有使用Promise机制的老代码中使用async函数时,你可能除了直接使用Promise之外没有别的选择。
下面是一个在XMLHttpRequest中使用Promise的示例:
function httpGet(url, responseType="") {
return new Promise(
function (resolve, reject) {
const request = new XMLHttpRequest();
request.onload = function () {
if (this.status === 200) {
// Success
resolve(this.response);
} else {
// Something went wrong (404 etc.)
reject(new Error(this.statusText));
}
};
request.onerror = function () {
reject(new Error(
'XMLHttpRequest Error: '+this.statusText));
};
request.open('GET', url);
xhr.responseType = responseType;
request.send();
});
}XMLHttpRequest的 API 设计都是基于 callback 的。使用async函数就意味着你要在内层回调函数里使用return和throw返回Promise对象的状态,但这肯定是不可能的。因此,在这情况中使用async的风格就是:
- 使用
Promise直接构建一个异步的基元 - 通过
async函数来使用这些基元
在一个模块或者 script 的顶级作用域中使用await,可以像下面这样:
async function main() {
console.log(await asyncFunction());
}
main();或者
(async function () {
console.log(await asyncFunction());
})();或者
(async () => {
console.log(await asyncFunction());
})();不用太担心那些未处理rejections,以前这种情况可能都是静默失败,不过现在大多数的现代浏览器都会抛出一个未处理的异常:
async function foo() {
throw new Error('Problem!');
}
foo();

