引入了 函数,使得异步操作变得async更加方便。async函数就是将 Generator 函数的星号(*
)替换成async,将yield
替换成await
,仅此而已。async函数对 Generator 函数的改进,体现在以下四点。
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(/([\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;
});
}
上面代码使用 Generator 函数遍历了每个动画,语义比 Promise 写法更清晰,用户定义的操作全部都出现在spawn函数的内部。这个写法的问题在于,必须有一个任务运行器,自动执行 Generator 函数,上面代码的spawn函数就是自动执行器,它返回一个 Promise 对象,而且必须保证yield语句后面的表达式,必须返回一个 Promise。
async 函数的写法:
async function chainAnimationsAsync(elem, animations){
let ret = null;
try {
for(let anim of animations) {
ret = await anim(elem);
}
} catch(e) {
//忽略错误,继续执行
}
return ret;
}
1) 按照顺序完成异步操作,并发执行(节省时间)
async function logInOrder(urls) {
//并发读取远程URL
const textPromise = urls.map(async url => {
const response = await fetch(url);
return response.text();
});
//依次序输出
for (const textPromise of textPromise) {
console.log(await textPromise);
}
}
虽然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) {
let docs = [{},{},{}];
//报错
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);
}
}