async 函数
ES2017 标准引入了 async 函数,使得异步操作变得更加方便。
async 函数是什么?一句话,它就是 Generator 函数的语法糖。
async函数就是将 Generator 函数的星号(*)替换成async,将yield替换成await,仅此而已。
但是,async函数对 Generator 函数的改进,体现在以下四点。
(1)内置执行器。
Generator 函数的执行必须靠执行器,所以才有了co模块,而async函数自带执行器。也就是说,async函数的执行,与普通函数一模一样,只要一行。
asyncReadFile();
上面的代码调用了asyncReadFile函数,然后它就会自动执行,输出最后结果。这完全不像 Generator 函数,需要调用next方法,或者用co模块,才能真正执行,得到最后结果。
(2)更好的语义。
async和await,比起星号和yield,语义更清楚了。async表示函数里有异步操作,await表示紧跟在后面的表达式需要等待结果。
(3)更广的适用性。
co模块约定,yield命令后面只能是 Thunk 函数或 Promise 对象,而async函数的await命令后面,可以是 Promise 对象和原始类型的值(数值、字符串和布尔值,但这时会自动转成立即 resolved 的 Promise 对象)。
(4)返回值是 Promise。
async函数的返回值是 Promise 对象,这比 Generator 函数的返回值是 Iterator 对象方便多了。你可以用then方法指定下一步的操作。
进一步说,async函数完全可以看作多个异步操作,包装成的一个 Promise 对象,而await命令就是内部then命令的语法糖。
Promise 对象的状态变化
async函数返回的 Promise 对象,必须等到内部所有await命令后面的 Promise 对象执行完,才会发生状态改变,除非遇到return语句或者抛出错误。也就是说,只有async函数内部的异步操作执行完,才会执行then方法指定的回调函数。
其实,使用async函数是为了可以将 async函数内容的所有异步操作完成后才返回结果,而Generator函数是可以一步一步的保存状态,如果一次都把 next 调用,那么是不会等待异步操作的完成,会先返回结果。
function* getHello(ms) {
console.log('开始');
yield new Promise((resolve) => {
setTimeout(() => {
console.log('Hello');
resolve(12)
}, ms)
});
console.log('中间');
}
var f = getHello(2000);
var result = f.next();
result.value.then((data) => {
console.log(data);
})
console.log('3');
输出
开始
3
Hello // 2s后
12 // 2s后
上面代码可以看出,调用了 next 方法后返回的 Promise 对象仍然是处于 pending 状态,而不是等待其异步操作完成后才会返回结果。
async function getHello(ms) {
console.log('开始');
await new Promise((resolve) => {
console.log('Hello');
setTimeout(() => {
console.log('world');
resolve(12)
}, ms)
});
console.log('中间');
}
getHello(2000)
console.log('3');
输出
开始
hello
3
world // 2s后
中间 // 2s后
上面代码中,await 会是让程序处于等待状态,等异步操作完成返回结果后,才会执行后面的代码
不过,实际上,它们还是一样的,最后都是可以在函数外面处理异步操作完成后的结果。
await 命令
正常情况下,await命令后面是一个 Promise 对象,返回该对象的结果。如果不是 Promise 对象,就直接返回对应的值。
async function f() {
// 等同于
// return 123;
return await 123;
}
f().then(v => console.log(v))
// 123
使用注意点
第一点,前面已经说过,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);
});
}
await 命令阻塞问题
先看一组代码
async function async1() {
console.log('async1 start')
await async2()
console.log('async1 end')
}
async function async2() {
console.log('async2')
}
async1()
console.log('script start')
这组代码的输出结果为
//async1 start
//async2
//script start
//async1 end
再看另外一组代码
async function async1() {
console.log('async1 start')
await new Promise((resolve)=> {
setTimeout(()=> {
resolve();
}, 1000)
}).then(()=>{
console.log("resolve")
})
console.log('async1 end')
}
async1()
console.log('script start')
这一组代码的输出结果为
//async1 start
//script start
//resolve
//async1 end
从上面代码可以看出来,await 命令后面的代码相当于then 方法,会被 await 命令阻塞,只有当async外的同步代码执行完毕,才再回到async内部等promise状态达到fulfill的时候(如果有 promise)再继续执行下面的代码。
const { resolve } = require("path");
async function async1() {
console.log("async1 start");
await async2();
console.log("async1 end");
}
async function async2() {
console.log("async2");
}
console.log("script star");
setTimeout(() => {
console.log("setTimeout");
}, 0);
async1();
new Promise((resolve) => {
console.log("promise1");
resolve();
}).then(() => {
console.log("promise2");
});
console.log("script end");
结果为
//script star
//async1 start
//async2
//promise1
//script end
//async1 end
//promise2
//setTimeou
posted on
浙公网安备 33010602011771号