js异步编程——async的用法

生成器的简单回顾

出现的问题

​ 在生成器的时候,我们说可以利用生成器的方式去完成一段异步回调。这样的话可以避免多个promise对象的级联造成代码的冗杂和难以维护。但是在运用生成器的时候,下面的这段代码会出现问题:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
const readFile = function () {
return new Promise(function (resolve, reject) {
setTimeout(()=>{
console.log('aaaa')
resolve('hhh')
},1000)
});
};

const gen = function* () {
const f1 = yield readFile();
const f2 = yield readFile();
console.log(f1);
console.log(f2);
};
var test = gen()
test.next();
test.next();
test.next();
//运行的结果是:
// undefined
// undefined
// aaaa
// aaaa

解决的方案

​ 可以看到,上面的这段代码本来我们预期的结果应该是等到前面的异步代码执行命完毕之后再执行同步代码。但是这个时候由于我们连续调用了三次next方法,这时候同步的操作被先执行了。这是有问题的,我们需要在异步操作之后再进行后面的操作,如果非要用生成器来实现的话,可以用下面的方式来解决:

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
30
31
32
33
34
35
36
const readFile = function () {
return new Promise(function (resolve, reject) {
setTimeout(()=>{
console.log('aaaa')
resolve('hhh')
},2000)
});
};

const gen = function* () {
const f1 = yield readFile();
const f2 = yield readFile();
console.log(f1);
console.log(f2);
};
var test = gen()
test.next = (function(func){
return function(){
var obj = func.apply(test,arguments);
var aa = obj.value;
if(obj.done)
return;
if(aa instanceof Promise){
// console.log('这是一个异步操作')
aa.then((args)=>{
console.log(args)
test.next()
return args;
})
}else{
// console.log('这是一个同步操作')
test.next()
}
}
})(test.next)
test.next();

​ 这样的话发现代码是按照正常的顺序执行了,但是还是有个问题,我们的f1和f2的返回值还是一个undefined,这是因为上面的函数很难返回一个值,如果非要这样的话目前我想到的办法是在生成器中声明一个对象,在每次执行异步操作的时候都传入这个参数,并且每次都会去改变对应的值,这样的话就能够实现上述功能:

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
30
31
32
33
34
35
36
37
38
39
40
41
const readFile = function (args) {
return new Promise(function (resolve, reject) {
setTimeout(()=>{
console.log('aaaa')
args.f1 = 2
args.f2 = 32
resolve('hhh')
},1000)
});
};

const gen = function* () {
var canshu = {
f1:undefined,
f2:undefined
}
yield readFile(canshu);
yield readFile(canshu);
console.log(canshu.f1);
console.log(canshu.f2);
};
var test = gen()
test.next = (function(func){
return function(){
var obj = func.apply(test,arguments);
var aa = obj.value;
if(obj.done)
return;
if(aa instanceof Promise){
// console.log('这是一个异步操作')
aa.then((args)=>{
test.next()
return args;
})
}else{
// console.log('这是一个同步操作')
test.next()
}
}
})(test.next)
test.next();

​ 上述代码是我们运用生成器来写的一个按正常代码顺序执行混合异步和同步操作的一个例子。实质是如果当前的操作是一个异步操作,那么他的下一个操作是放在他的Promise对象中的then方法里的,只有在当前的异步操作执行完毕的时候才会进行后续的操作。但是如果当前的操作是个同步的操作。那么就直接执行后续的操作,这样就实现了我们正常的代码顺序。

通过async实现的方法

一个实例

​ 但是可以看得出,由生成器来解决异步问题,需要我们自己去改写Next方法,代码比较复杂,并且返回参数的时候是不容易的。为了更简单的操作异步程序,ES7语法为我们提供了一个更简单的操作方式——使用async来处理。实际上async就是Generator的语法糖。现在我们用async来写刚刚我们的异步操作的例子,可以得到:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const setFunc = function asyncFunction(args1){
return new Promise((resolve) => {
setTimeout(()=>{
console.log('nihao')
resolve(args1)
},1000)
})
}
const gen =async function() {
const f1 = await setFunc('this is f1');
console.log(f1);
const f2 = await setFunc('this is f2');
console.log(f2);
};
var test = gen()

​ 首先这里定义了一个异步的函数setFunc(),之后对这个setFunc进行一些操作,这个函数返回了一个Promise对象,其中进行了一些异步的操作。之后用await执行这个异步的操作,之后在异步操作之后进行了一些同步的操作最后的输出结果表明,在async函数里,通过await声明的函数可以进行相关的异步操作,并且后面的操作会等到这个异步操作结束之后进行,实质是根据生成器的原理进行的相关操作。

async函数返回值

​ async函数返回的是一个Promise对象,其内部的return语句会被认为是调用了resolve函数,返回值即是这个函数的参数。同样,在async函数里面也可以抛出一个异常,会使得Promise调用reject函数并catch住这个异常。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
async function f1(){
return 'func1'
}
async function f2(){
throw new Error('Something error')
}
f1().then((args)=>{
console.log('this is ' + args)
})
f2().then((args)=>{
console.log('this is ' + args)
},(args)=>{
console.log('this is an error\n' + args)
})

await命令

​ await一般后面跟的是一个Promise对象,如果不是的话就会返回对应的值。

​ 尤其需要注意的是,如果await后面的Promise对象是reject,那么就会给回调函数中的catch接收,但是这里的问题就是,如果前面出现了reject,对应后面的内容就不能执行了。这时候就需要在await的异步操作后面加上catch操作,从而进行异常的处理。

1
2
3
4
5
6
7
8
9
10
const gen =async function() {
const f1 = await setFunc('this is f1').catch(()=>{
console.log('test1')
});
console.log(f1);
const f2 = await setFunc('this is f2').catch(()=>{
console.log('test2')
});
console.log(f2);
};

使用的注意点

####同时进行异步操作

​ 由于await实际上是将异步转化成同步的一种方式,对于几个相互独立的异步操作,应该让其同时进行异步操作,而不应该用await让被一个进程阻塞其他进程的进行,这样会十分耗时。应该用下面的两种写法让他们同时执行:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
const setFunc1 = function asyncFunction(args1){
return new Promise((resolve,reject) => {
setTimeout(()=>{
console.log('nihao')
resolve(args1)
},1000)
})
}
const setFunc2 = function asyncFunction(args1){
return new Promise((resolve,reject) => {
setTimeout(()=>{
console.log('nihao')
resolve(args1)
},1000)
})
}
const gen2 = async function(){
const [f1,f2] = await Promise.all([setFunc1(),setFunc2()])
console.log('this is the end')
}
gen2()

当然,如果非要用await,上面的代码也可以转化成一下的代码:

1
2
3
4
let fooPromise = getFoo();
let barPromise = getBar();
let foo = await fooPromise;
let bar = await barPromise;

两者都是等效的,都可以让多个独立的异步操作在同一时间进行。

在普通函数中

首先await必须要在async声明的函数里面,否则会报错。首先看下面的这个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function myPost(data){
return new Promise((resolve)=>{
setTimeout(()=>{
console.log('This is ' + data)
},1000)
})
}
function myTest(){
const docs = [
{aa:'ss'},
{bb:'uu'},
{cc:'oo'}
]
docs.forEach(async function(doc){
await myPost(JSON.stringify(doc))
})
}
myTest()

这样写的await运行出来的结果一般都不是我们期望的,一个结束之后在运行另外一个,而是三个同时在运行,是并发执行。正确的方式应该是:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function myPost(data){
return new Promise((resolve)=>{
setTimeout(()=>{
console.log('This is ' + data)
resolve()
},1000)
})
}
async function myTest(){
const docs = [
{aa:'ss'},
{bb:'uu'},
{cc:'oo'}
]
for(let doc of docs){
await myPost(JSON.stringify(doc))
}
}
myTest()

​ 注意在每个异步操作里面都应该加上resolve(),不然程序只会输出JSON数据的第一项之后就不再输出。如果这时候确实有一个需求是前面需要并发地执行几个操作,后面再对他们的结果进行一些处理的话,这时候就需要用下面的两种写法来实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
async function test(){
let obj = [
{name:'liming'},
{age:18},
{sex:'man'}
]
let promises = obj.map((bd) => myPost(JSON.stringify(bd)));
await Promise.all(promises);
console.log('This is the end')
}
//第二种写法
async function test(){
let obj = [
{name:'liming'},
{age:18},
{sex:'man'}
]
let promises = obj.map((bd) => myPost(JSON.stringify(bd)));
await Promise.all(promises);
console.log('This is the end')
}

顶层await

​ 目前,esm模块加载器支持顶层await,即这时候await命令可以不放在async函数内,直接使用即可。但是这种写法的脚本必须使用esm加载器,否则不能执行。

0%