含义及作用
引入了函数,使得异步操作变得更加方便.async函数就是将Generator函数的星号(*)替换成async,将yield替换成await,仅此而已.async函数对Generator函数的改进,体现在以下四点.
- 内置执行器
- 更好的语义
- 更广的适用性
- 返回值是promise
基本用法
async函数返回一个Promise对象,可以使用then方法添加回调函数,当函数执行的时候,一旦遇到await就会先返回等到异步操作完成,再接着执行函数体内后面的语句.
async function getStockPriceByName(name) {
const symbol = await getStockPriceByName(name);
const stockPrice = await getStockPriceByName(symbol);
return stockPrice;
}
getStockPriceByName("goog").then(function(result) {
console.log(result);
})
async函数返回的promise对象,必须等到内部所有await命令后面的promise对象执行完,才会发生状态变化除非遇到return语句或者抛出错误.也就是说,只有等async函数内部的异步操作对象执行完,才会执行then方法制定的回调函数
async function getTitle(url) {
let response = await fetch(url);
let html = await resoponse.text();
return html.match(/<title>([\s\S] + ) <\/title>/i)[i];
}
getTitle('https://tc39.github.io/ecma262/').then(console.log)
正常情况下,await命令后面是一个Promise对象,返回该对象的结果.如果不是promise对象,就直接返回对应的值await命令后面是一个thenable对象(即定义then的对象),那么await会将其等同于Promise对象
class Sleep{
constructor(timeout){
this.timout=timout;
}
then(resolve,reject){
const startTime = Date.now();
setTimeout(
() => resolve(Date.now() - startTime),
this.timeout
);
}
}
(async () => {
const.actualTime = await new Sleep(1000);
console.log(actualTime);
})();
错误处理
使用try…catch结构,实现多次重复尝试,await后面的promise对象会抛出一个错误对象,导致catch方法的回调函数被配出的错误对象调用.
const superagent = require('superagent');
const NUM_RETRIES = 3;
async function test() {
let i;
for (i = 0; i < NUM_RETRIES; ++i) {
try{
await superagent.get('http://google.com/this-throw-an-error');
break;
} catch (err) {
}
}
console.log(i);
}
test();
与其他异步处理方法的比较:
async函数的实现原理:将Gennerator函数和自动执行器,包装在一个函数里
async函数与Promise,Generator函数的比较案例:
假定某个DOM元素上面,部署了一系列的动画,前一个动画结束,才能开始后一个,如果当中有一个动画出错,就不在往下执行,返回上一个成功执行的动画的返回值.
Promise的写法
function chainAnimationsPromise(elem, animations) {
//变量ret用来保存上一个动画的返回值
let ret = null;
//新建一个空的promise
let p = Promise.resolve();
//使用then方法。添加所有动画
for(let anim of animations) {
p = p.then(function(val) {
ret = val;
return anim(elem);
});
}
//返回一个部署了错误捕捉机制的Promise
return p.catch(function(e) {
//忽略错误,继续执行
}).then(function() {
return ret;
});
}
虽然Promise的写法比回调函数的写法大大改进,但是一眼看上去,代码完全都是Promise的API(then,catch等等),操作本身的语义反而不容易看出来.
Generator函数的写法
function chainAnimationsGenerator(elem, animations) {
return spawn(function*(){
let ret = null;
try{
for(let anim of animations){
ret == yield anim(elem);
}
}catch(e){
//忽略错误,继续执行
}
return ret;
});
}
虽然map方法的参数是async函数,但它是并发执行的,因为只有async函数内部是继发执行,外部不受影响.后面的for…of循环内部使用await,因此实现了按顺序输出.
for await…of
for…of循环用于遍历同步的Iterator接口,新引入的for await…of循环,则是用于遍历异步的Iterator接口
async function f(){
for await (const x of createAsyncInterable(['a','b'])){
console.log(x);
}
}
//a
//b
注意:
(1) await命令后面的promise对象,运行结果可能是rejected,所以最好把await命令放在try…catch代码块中
async function myFunction(){
try{
await somethingThatReturnAPromise();
}catch(err){
console.log(err);
}
}
//另一种写法
async function myFunction(){
await somethingThatReturnAPromise().catch(function(err){
console.log(err);
});
}
(2)多个await命令后面的异步操作,如果不存在继发关系,最好让它们同时出发,继发写法(不推荐):比较耗时,执行完一个才能执行下一个
let foo = await getFoo();
let bar = await getBar();
同时触发(推荐):缩短程序的执行时间
//写法一
let [foo,bar] = await Promise.all([getFoo(),getBar()]);
//写法二
let fooPromise = getFoo();
let barPromise = getBar();
let foo = await fooPromise;
let bar = await barPromise;
(3)await命令只能在async函数之中,如果在普通函数就会报错
async function dbFunc(db) {
//报错
docs.forEach(function (doc){
await db.post(doc);
});
}
//报错,因为await用在普通函数之中了
function dbFunc(db){
//这里你需要async
let docs = [{},{},{}];
//可能得到错误结果
docs.forEach(async function(doc){
await db.post(doc);
});
}
//原因是这时三个db.post操作将是并发执行,也就是同时执行,而不是继发执行
//正确写法是采用for循环
async function dbFunc(db){
let docs = [{},{},{}];
for(let doc of docs){
await db.post(doc);
}
}