es6之async函数

async函数
async函数是Generator函数的语法糖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var fs = require('fs');
var readFile = function(fileName){
return new Promise((resolve,reject)=>{
fs.readFile(fileName,function(err,data){
if(err) reject(err);
resolve(data);
})
})
};

var gen = function* (){
var f1 = yield readFile('/etc/fir');
var f2 = yield readFile('/etc/sec');

console.log(f1.toString());
console.log(f2.toString());
}

写成async:

1
2
3
4
5
6
var asyncReadFile = async function(){
var f1 = await readFile('/etc/fir');
var f2 = await readFile('/etc/sec');
console.log(f1.toString());
console.log(f2.toString());
}

async函数对于Generator函数的改进有以下四点
1) 内置执行器
async函数的执行,与普通函数一样,只要一行

1
var result = asyncReadFile();

调用上面代码后,自会自动执行,输出最后的结果,不需要使用next方法,或例如co之类的模块

2) 更好的语义
async/await*/yield相比,语义更好,async表示函数内部有异步操作,await表示紧跟在后面的表达式需要等待结果

3) 更广的适用性
co模块规定,yield命令后只能是thunk函数或Promise对象,而async函数的await命令后面,可以是Promise、原始类型的值(数值、字符串、布尔值等,但这时等同于同步操作)

4) 返回值是Promise
因为Generator返回的是Iterator对象,所以async可以使用then指定下一步操作

用法

当函数执行的时候,遇到await就停止,等到异步操作完成,再执行后面的语句

1
2
3
4
5
6
7
8
9
async function getStockPriceByName(name){
var symbol = await getStockSymbol(name);
var stockPrice = await getStockPrice(symbol);
return stockPrice;
}

getStockPriceByName('zhu').then(result=>{
console.log(result)
})

调用上面函数的时候会立即返回Promise对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function timeout(ms){
return new Promise(resolve=>{
setTimeout(resolve,ms)
})
}

async function asyncPrint(value,ms){
await timeout(ms);
return 'hw'
}

asyncPrint('zhu',1000).then(result=>{
console.log(result);//1s后:'hw'
})

asnyc函数的形式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
//1) 函数声明
async function (){};

//2) 函数表达式
var foo = async function(){};

//3) 对象
let obj = {
async foo(){}
};
obj.foo().then(...)

//4) class类
class Storage{
constructor(){
this.cachePromise = caches.open('avatars')
}

async getAvatars(name){
const cache = await this.cachePromise;
return cache.match(`/avatars/${name}.jpg`);
}
}

const storage = new Storage();
storage.getAvatar('jake').then(...);

//5) 箭头函数
const foo = async ()=>{}

语法

返回Promise

async函数内部的return语句返回的值,会成为then方法回调函数的参数

1
2
3
4
5
6
async function f(){
return 'zhu'
}

f().then(v=>console.log(v));
//'zhu'

如果async函数内部抛出错误,会导致返回的Promise变成reject,抛出的错误对象会被catch方法回调函数接收到

1
2
3
4
5
6
7
async function f(){
throw new Error('error了')
}
f().then(
v=>console.log(v),
e=>console.log(e) //[Error: error了]
)

Promise对象的状态变化

async函数返回的promise对象,必须等到内部所有的await命令后面的promise对象执行完,才会有状态改变,除非有return语句或抛出错误,即只有async函数内部的异步操作全部执行完,then方法才会执行

await命令

一般情况下,await后面是一个promise对象,如果不是,就先转换为一个立即resolve的Promise对象

1
2
3
4
5
async function f(){
return await 222;
}

f().then(v=>console.log(v)); //222

await后面的Promise对象如果为reject,那么reject的参数就会被catch方法的回调函数捕获

1
2
3
4
5
6
7
async function f(){
await Promise.reject('err')
}

f()
.then(v=>console.log(v))
.catch(e => console.log(e))

即使async里面没有return,但仍能被捕获

只要有一个await后面的Promise变为reject,那么整个async函数都会中断执行
如何在前一个异步操作失败后,仍能继续执行后面的异步呢?

1) 可以将第一个await放在try...catch结构里面,这样不管第一个成功与否,第二个都能执行,如果有多个也可以放在try...catch之中

1
2
3
4
5
6
7
8
9
10
11
12
async function f(){
try{
await Promise.reject('出错了')
}catch(e){

}

return await Promise.resolve('zhu')
}

f()
.then(v=>console.log(v))

2) 在await后面的promise对象再跟一个catch方法,处理前面出现的错误

1
2
3
4
5
6
7
8
async function f(){
await Promise.reject('错误')
.catch(e=>console.log(e));
return await Promise.resolve('zhu')
}

f()
.then(v=>console.log(v))

注意事项

1) await后面的Promise对象结果可能是rejected,所以最好把await放在try...catch之中

1
2
3
4
5
6
7
8
9
10
11
12
13
async function myFn(){
try{
await somethingTahtReturnAsPromise();
}catch(err){
console.log(err)
}
}

//另一种写法
async function myFn(){
await somethingTahtReturnAsPromise()
.catch(e=>console.log(e))
}

2) 多个await后面的异步操作,如果不存在继发关系,最好让他们同时触发

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

上面两个异步操作互相不依赖,写成上面会比较耗时,只有在getFoo之后才会执行getBar,可以将他们写成同时触发

1
2
3
4
5
6
7
8
//写法一
let [foo,bar] = await Promise.all([getFoo(),getBar()]);

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

3) await命令只能用在async函数之中,如果在普通函数会报错

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
async function dbFunc(){
let docs = [{},{},{}];

//报错
docs.forEach(doc=>{
await db.post(doc);
});

//同样报错
docs.forEach(async doc=>{
await db.post(doc);
});

//正确使用for循环
for (let doc of docs) {
await db.post(doc);
}
}

async函数的实现原理

async函数就是将Generator函数和自动执行器包装在一个函数里面

1
2
3
4
5
6
7
8
9
10
async function fn(args){
//...
}

//等同于
function fn(){
return spawn(function* (){ //spawn是自动执行器
//...
})
}

spawn的实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function spawn(genF){
return new Promise(function(resolve,reject){
var gen = genF();
function step(nextF){
try{
var next = nextF();
}catch(e){
return reject(e)
}

if(next.done){
return resolve(next.value)
}

Promise.resolve(next.value).then(function(v){
step(function(){return gen.next(v)})
},function(e){
step(function(){return gen.throw(e)})
})
}
step(function(){return gen.next(undefined)})
})
}

与其他异步方法的比较

需求:某个 DOM 元素上面,部署了一系列的动画,前一个动画结束,才能开始后一个。如果当中有一个动画出错,就不再往下执行,返回上一个成功执行的动画的返回值。

1) Promise:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function chainAniPromise(ele,animation){
var ret = null; //保存上一步动画返回值

var p = Promise.resolve();

for(let anim of animation){ //使用then,添加所有动画,很好
p = p.then(function(value){
ret = value;
return ainm(ele)
})
}
return p.catch(e=>{
//忽略错误,继续执行
}).then(() => ret)
}

2) generator

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function chainAniPromise(ele,animation){
return spawn(function*(){
var ret = null;

try{
for(let anim of animation){
ret = yield anim(ele)
}
}catch(e){
//忽略错误,继续执行
}

return ret;
})
}

3) async

1
2
3
4
5
6
7
8
9
10
11
async function chainAniPromise(ele,animation){
var ret = null;
try{
for(var anim of animation){
ret = await ainm(ele)
}
}catch(e){
//忽略错误,继续执行
}
return ret
}

异步遍历器

Iterator接口呢只要调用对象的遍历器对象的接口,就会等到一个{value,done}对象,然而这里的next方法必须是同步的,即一旦执行next,就必须同步得到value/done两个属性,这对于异步来说就不适合,所以引入了thunk函数/promise对象,value是一个thunk/promise,等待以后返回的真正的值,而done
还是同步产生

异步遍历器即为操作提供原生的遍历器接口,value/done两个属性都是异步产生

异步遍历的接口

最大语法特点就是调用遍历器的next方法,返回一个promise

1
2
3
4
5
6
7
asyncIterator
.next()
.then(
({value,done}=>{
//....
})
)

异步遍历器部署在对象的Symbol.asyncIterator属性上

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
const asyncIterable = createAsyncIterator(['a','b']);
const asyncIterator = asyncIterable[Symbol.asyncIterator]();

asyncIterator
.next()
.then(iterResult1 =>{
console.log(iterResult1); // { value: 'a', done: false }
return asyncIterator.next();
})
.then(iterResult2 =>{
console.log(iterResult2); // { value: 'b', done: false }
return asyncIterator.next();
})
.then(iterResult3 =>{
console.log(iterResult3); //{ value: undefined, done: true }
})

//改为async
async function f() {
const asyncIterable = createAsyncIterable(['a', 'b']);
const asyncIterator = asyncIterable[Symbol.asyncIterator]();
console.log(await asyncIterator.next());
// { value: 'a', done: false }
console.log(await asyncIterator.next());
// { value: 'b', done: false }
console.log(await asyncIterator.next());
// { value: undefined, done: true }
}

由于异步便利的next可以连续调用,不必等到上一步产生的Promise对象resolve以后再调用

1
2
3
4
5
6
const asyncGenObj = createAsyncIterable(['a','b']);
const [{value:v1},{value:v2}] = await Promise.all([
asyncGenObj.next(),asyncGenObj.next()
]);

console.log(v1,v2); //a,b

for await…of

遍历异步的Iterator接口

1
2
3
4
5
async function f(){
for await(const x of createAsyncIterable(['a','b'])){
console.log(x)
}
}

createAsyncIterable返回一个异步遍历器,for…of自动调用next方法,返回Promise对象,用await来处理Promise对象,一旦resolve,就把值(x)传入for...of

也可以用于同步遍历器

1
2
3
4
5
(async function f{
for await (const x of ['a','b']){
console.log(x)
}
})();

异步Generator函数

async函数与Generator函数结合

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
async function* readLines(path){
let file = await fileOpen(path);

try{
while(!file){
yield await file.readLine()
}
}finally{
await file.close()
}
}

for await (const line of readLines(filePath)){
console.log(line)
}

for await...of结合

1
2
3
4
5
async function* preFixLines(asyncIterable){
for await (const line of asyncIterable){
yield '> '+line;
}
}

yield依然立刻返回,但返回的是一个Promise

文章目录
  1. 1. 用法
  2. 2. 语法
  • 返回Promise
  • Promise对象的状态变化
    1. 1. await命令
    2. 2. 注意事项
    3. 3. async函数的实现原理
    4. 4. 与其他异步方法的比较
    5. 5. 异步遍历器
  • 异步遍历的接口
    1. 1. for await…of
    2. 2. 异步Generator函数