什么是async函数?

ES7 提供了async函数,使得异步操作变得更加方便。async函数是什么?一句话,async函数就是 Generator 函数的语法糖。
如下代码是一个 Generator 函数,依次读取两个文件。

var fs = require('fs');
var readFile = function (fileName) {
    
    
return new Promise(function (resolve, reject) {
    
    
fs.readFile(fileName, function(error, data) {
    
    
if (error) reject(error);
resolve(data);
});
});
};
var gen = function* (){
    
    
var f1 = yield readFile('/etc/fstab');
var f2 = yield readFile('/etc/shells');
console.log(f1.toString());
console.log(f2.toString());
};

写成async函数如下:

var asyncReadFile = async function (){
    
    
var f1 = await readFile('/etc/fstab');
var f2 = await readFile('/etc/shells');
console.log(f1.toString());
console.log(f2.toString());
};

一比较就会发现,async函数就是将 Generator 函数的星号(*)替换成async,将yield替换成await,仅此而已。
async函数对 Generator 函数的改进,体现在以下四点。
( 1 )内置执行器。
Generator 函数的执行必须靠执行器,所以才有了co模块,而async函数自带执行器。也就是说,async函数的执行,与普通函数一模一样,只要一行。

var result = asyncReadFile();

上面的代码调用了asyncReadFile函数,然后它就会自动执行,输出最后结果。这完全不像 Generator 函数,需要调用next方法,或者用co模块,才能得到真正执行,得到最后结果。
( 2 )更好的语义。
async和await,比起星号和yield,语义更清楚了。async表示函数里有异步操作,await表示紧跟在后面的表达式需要等待结果。
( 3 )更广的适用性。
co模块约定,yield命令后面只能是 Thunk 函数或 Promise 对象,而async函数的await命令后面,可以是 Promise 对象和原始类型的值(数值、字符串和布尔值,但这时等同于同步操作)。
( 4 )返回值是 Promise 。
async函数的返回值是 Promise 对象,这比 Generator 函数的返回值是 Iterator 对象方便多了。你可以用then方法指定下一步的操作。
进一步说,async函数完全可以看作多个异步操作,包装成的一个 Promise 对象,而await命令就是内部then命令的语法糖。

async语法
async函数的语法规则总体上比较简单,难点是错误处理机制。
( 1 )async函数返回一个 Promise 对象。
async函数内部return语句返回的值,会成为then方法回调函数的参数。

async function f() {
    
    
return 'hello world';
}
f().then(v => console.log(v))
// "hello world

( 2 )async函数返回的 Promise 对象,必须等到内部所有await命令的 Promise 对象执行完,才会发生状态改变。也就是说,只有async函数内部的异步操作执行完,才会执行then方法指定的回调函数。
下面是一个例子。

async function getTitle(url) {
    
    
let response = await fetch(url);
let html = await response.text();
return html.match(/<title>([\s\S]+)<\/title>/i)[1];
}
getTitle('https://tc39.github.io/ecma262/').then(console.log)
// "ECMAScript 2017 Language Specification"

( 3 )正常情况下,await命令后面是一个 Promise 对象。如果不是,会被转成一个立即

resolve的 Promise 对象。
async function f() {
    
    
return await 123;
}
f().then(v => console.log(v))
// 123

上面代码中,await命令的参数是数值123,它被转成 Promise 对象,并立即resolve。

( 4 )如果await后面的异步操作出错,那么等同于async函数返回的 Promise 对象被reject。

async function f() {
    
    
await new Promise(function (resolve, reject) {
    
    
throw new Error(' 出错了 ');
});
}
f()
.then(v => console.log(v))
.catch(e => console.log(e))
// Error :出错了

上面代码中,async函数f执行后,await后面的 Promise 对象会抛出一个错误对象,导致catch方法的回调函数被调用,它的参数就是抛出的错误对象。具体的执行机制,可以参考后文的 “async 函数的实现 ” 。
防止出错的方法,也是将其放在try…catch代码块之中。

async function f() {
    
    
try {
    
    
await new Promise(function (resolve, reject) {
    
    
throw new Error(' 出错了 ');
});
} catch(e) {
    
    
}
return await('hello world');
}

Async 函数有多种使用形式。

//  函数声明
async function foo() {
    
    }
//  函数表达式
const foo = async function () {
    
    };
//  对象的方法
let obj = {
    
     async foo() {
    
    } };
//  箭头函数
const foo = async () => {
    
    };

注意点:
1.await命令后面的 Promise 对象,运行结果可能是 rejected ,所以最好把await命令放在try…catch代码块中。

async function myFunction() {
    
    
try {
    
    
await somethingThatReturnsAPromise();
} catch (err) {
    
    
console.log(err);
}
}
//  另一种写法
async function myFunction() {
    
    
await somethingThatReturnsAPromise()
.catch(function (err) {
    
    
console.log(err);
};
}

第二点,多个await命令后面的异步操作,如果不存在继发关系,最好让它们同时触发。

let foo = await getFoo();
let bar = await getBar();

上面代码中,getFoo和getBar是两个独立的异步操作(即互不依赖),被写成继发关系。这样比较耗时,因为只有getFoo完成以后,才会执行getBar,完全可以让它们同时触发。
// 写法一

let [foo, bar] = await Promise.all([getFoo(), getBar()]);

// 写法二

let fooPromise = getFoo();
let barPromise = getBar();
let foo = await fooPromise;
let bar = await barPromise;

上面两种写法,getFoo和getBar都是同时触发,这样就会缩短程序的执行时间。

第三点,await命令只能用在async函数之中,如果用在普通函数,就会报错。

async function dbFuc(db) {
    
    
let docs = [{
    
    }, {
    
    }, {
    
    }];
//  报错
docs.forEach(function (doc) {
    
    
await db.post(doc);
});
}

猜你喜欢

转载自blog.csdn.net/qq_42526440/article/details/114636924