My Little World

异步函数generator和async

generator/yield/next

generator函数即生成器函数,可以理解为用于产生生成器的函数
yield 关键字是只能用于generator函数,作为异步断点,相当于continue+return功能
next 关键字是生成器用于执行生成器函数的关键字,
即每调用一次,执行到yield定义的语句,把yield 后面的表达式结果当做返回值返回来
如果调用时传递了参数,则覆盖上次yield 的结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function* helloworld(){
yield 'hello';
yield 'world';
let aa = 0;
let a = yield aa++; //yield赋值不用小括号
console.log(a);
yield 123+1;
console.log('Hello' + (yield 123)); //使用yield表达式用小括号
return 'endig';
}
let obj = helloworld();//并不会执行generator函数,只是生成生成器
obj.next();//{value: "hello", done: false}
obj.next();//{value: "world", done: false}
obj.next();//{value: 0, done: false}
obj.next('456');// 456 {value: 124, done: false} 如果不传值的话,a的值为undefined
obj.next();//{value: 123, done: false}
obj.next();// Helloundefined {value: "endig", done: true}; 不传参,yield返回undefined

next()是将yield表达式替换成一个值。
throw()是将yield表达式替换成一个return语句。
return()是将yield表达式替换成一个return语句。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
const g = function* (x, y) {
let result = yield x + y;
return result;
};

const gen = g(1, 2);
gen.next(); // Object {value: 3, done: false}

gen.next(1); // Object {value: 1, done: true}
// 相当于将 let result = yield x + y
// 替换成 let result = 1;

gen.throw(new Error('出错了')); // Uncaught Error: 出错了
// 相当于将 let result = yield x + y
// 替换成 let result = throw(new Error('出错了'));

gen.return(2); // Object {value: 2, done: true}
// 相当于将 let result = yield x + y
// 替换成 let result = return 2;

如果yield表达式后面跟的是一个遍历器对象,需要在yield表达式后面加上星号,表明它返回的是一个遍历器对象。这被称为yield表达式
yield
后面的 Generator 函数(没有return语句时),等同于在 Generator 函数内部,部署一个for…of循环。
for…of循环可以自动遍历Generator函数时生成的Iterator对象,且此时不再需要调用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
37
38
39
40
41
42
43
44
45
46
例1
function* inner() {
yield 'hello!';
}

function* outer1() {
yield 'open';
yield inner();
yield 'close';
}

var gen = outer1()
gen.next().value // "open"
gen.next().value // 返回一个遍历器对象
gen.next().value // "close"

function* outer2() {
yield 'open'
yield* inner()
yield 'close'
}

var gen = outer2()
gen.next().value // "open"
gen.next().value // "hello!"
gen.next().value // "close"

例2
let delegatedIterator = (function* () {
yield 'Hello!';
yield 'Bye!';
}());

let delegatingIterator = (function* () {
yield 'Greetings!';
yield* delegatedIterator;
yield 'Ok, bye.';
}());

for(let value of delegatingIterator) {
console.log(value);
}
// "Greetings!
// "Hello!"
// "Bye!"
// "Ok, bye."

如果yield*后面跟着一个数组,由于数组原生支持遍历器,因此就会遍历数组成员。

1
2
3
4
5
function* gen(){
yield* ["a", "b", "c"];
}

gen().next() // { value:"a", done:false }

上面代码中,yield命令后面如果不加星号,返回的是整个数组,加了星号就表示返回的是数组的遍历器对象。

Generator 函数返回的遍历器对象,可以继承prototype,但不能当做普通构造函数,不会返回this

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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
function* g() {
this.a = 11;
}

let obj = g();
obj.next();
obj.a // undefined
//可以进行改造
//执行的是遍历器对象f,但是生成的对象实例是obj
function* F() {
this.a = 1;
yield this.b = 2;
yield this.c = 3;
}
var f = F.call(F.prototype);

f.next(); // Object {value: 2, done: false}
f.next(); // Object {value: 3, done: false}
f.next(); // Object {value: undefined, done: true}

f.a // 1
f.b // 2
f.c // 3

function* F() {
this.a = 1;
yield this.b = 2;
yield this.c = 3;
}
var f = F.call(F.prototype);

f.next(); // Object {value: 2, done: false}
f.next(); // Object {value: 3, done: false}
f.next(); // Object {value: undefined, done: true}

f.a // 1
f.b // 2
f.c // 3
function* gen() {
this.a = 1;
yield this.b = 2;
yield this.c = 3;
}

function F() {
return gen.call(gen.prototype);
}

var f = new F();

f.next(); // Object {value: 2, done: false}
f.next(); // Object {value: 3, done: false}
f.next(); // Object {value: undefined, done: true}

f.a // 1
f.b // 2
f.c // 3

实际应用

状态机

1
2
3
4
5
6
7
8
var clock = function*() {
while (true) {
console.log('Tick!');
yield;
console.log('Tock!');
yield;
}
};

异步操作的同步化写法

IMPORTANT!!!

Generator函数的暂停执行的效果,意味着可以把异步操作写在yield语句里面,等到调用next方法时再往后执行。这实际上等同于不需要写回调函数了,因为异步操作的后续操作可以放在yield语句下面,反正要等到调用next方法时再执行。所以,Generator函数的一个重要实际意义就是用来处理异步操作,改写回调函数。

只有当yield后面跟的函数先执行完,无论执行体里面有多少异步回调,都要等所有回调先执行完,才会执行等号赋值,以及再后面的操作。这也是yield最大的特性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function request(url) {
$.get(url, function(response){
it.next(response);
});
}

function* ajaxs() {
console.log(yield request('a.html'));
console.log(yield request('b.html'));
console.log(yield request('c.html'));
}

var it = ajaxs();

it.next();

// a.html
// b.html
// c.html

第1步:将所有异步代码的每一步都封装成一个普通的、可以有参数的函数,比如上面的request函数。上面例子三个异步代码却只定义了一个request函数,因为request函数能复用。如果不能复用的话,请老老实实定义三个普通函数,函数内容就是需要执行的异步代码。

第2步:定义一个生成器函数,把流程写进去,完全的同步代码的写法。生成器函数可以有参数。

第三步:定义一个变量,赋值为迭代器对象。迭代器对象可以加参数,参数通常将作为流程所需的初始值。

第四步:变量名.next()。不要给这个next()传参数,传了也没用,因为它找不到上一个yield语句。

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
//在上述每一步异步中间,都间隔3秒
function request(url) {
$.get(url, function(response){
it.next(response);
});
}

function sleep(time) {
setTimeout(function() {
console.log('I\'m awake.');
it.next();
}, time);
}

function* ajaxs(ur) {
console.log(yield request(ur));
yield sleep(3000);
console.log(yield request('b.html'));
yield sleep(3000);
console.log(yield request('c.html'));
}

var it = ajaxs('a.html');

it.next();

参考资料
参考资料

async/await

async 用于定义异步函数,相当于generator的*号,只不过async函数不需要像generator那样用next()去出发,只需要调用就能实现异步
await 用于async函数内部,作用是等待await 后面的异步流程结束后,执行asyn函数接下来的语句,相当于暂停符

async返回promise对象,因此可以连接then()或者catch()继续进行异步操作或者捕获错误
async函数内部return语句返回的值,会成为then方法回调函数的参数。
async函数返回的 Promise 对象,必须等到内部所有await命令后面的 Promise 对象执行完,才会发生状态改变,
除非遇到return语句或者抛出错误。
也就是说,只有async函数内部的异步操作执行完,才会执行then方法指定的回调函数。

正常情况下,await命令后面是一个 Promise 对象。如果不是,会被转成一个立即resolve的 Promise 对象。即可以等任意表达式的结果
因此 await 返回异步函数return的结果,或者异步函数返回的promise的resolve的参数

1
2
3
如果它等到的不是一个 Promise 对象,那 await 表达式的运算结果就是它等到的东西。

如果它等到的是一个 Promise 对象,await 就忙起来了,它会阻塞后面的代码,等着 Promise 对象 resolve,然后得到 resolve 的值,作为 await 表达式的运算结果。

只要一个await语句后面的 Promise 变为reject,那么整个async函数都会中断执行。
解决中断执行的方法有两种,
一种是把await放在try…catch…里面,例5
另一种是给await的promise链接catch{},例6

优化:如果两个异步操作没有任何联系,不存在继发关系,最好让他们同时触发 例8,例9

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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
例1.
async function lala(val){
let aa = await lili(val);
console.log(aa);//'red'
return 0;
}
function lili(val){
return new Promise((resolve,reject)=>{
resolve('red')
})
}
lala('kk').then(
val=>console.log(val),//0
err=>console.log(11111,err) //如果promise发生reject会传递到这里
);
例2.
async function lala(val){
let aa = await lili(val);
let bb = await bebe(val);
console.log(aa);
return 0;
}
function lili(val){
return new Promise((resolve,reject)=>{
resolve('red')
})
}
function bebe(val){
return new Promise((resolve,reject)=>{
reject('err') //产生错误
})
}
lala('kk').then(
val=>console.log(val),
err=>console.log(11111,err) //11111 "err" 只会打印这一处
);

例3.
async function f() {
return await 123;
}

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

例4.
async function f() {
await Promise.reject('出错了');
await Promise.resolve('hello world'); // 不会执行
}

例5.
async function f() {
try {
await Promise.reject('出错了');
} catch(e) {
}
return await Promise.resolve('hello world');
}

f()
.then(v => console.log(v))
// hello world

例6,
async function f() {
await Promise.reject('出错了')
.catch(e => console.log(e));
return await Promise.resolve('hello world');
}

f()
.then(v => console.log(v))
// 出错了
// hello world

例7. //同例5例6

async function myFunction() {
try {
await somethingThatReturnsAPromise();
} catch (err) {
console.log(err);
}
}

// 另一种写法

async function myFunction() {
await somethingThatReturnsAPromise()
.catch(function (err) {
console.log(err);
});
}

例8
//原来
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;

例9
async function logInOrder(urls) {
// 并发读取远程URL
const textPromises = urls.map(async url => {
const response = await fetch(url);
return response.text();
});

// 按次序输出
for (const textPromise of textPromises) {
console.log(await textPromise);
}
}

参考资料
相关资料