生成器_async_await

生成器

生成器是一种特殊的迭代器;是ES6中新增的一种函数控制、使用的方案,它可以让我们更加灵活的控制函数什么时候继续执行、暂停执行等。

生成器函数

js的使用的函数,函数的终止条件通常是返回值或者抛出了异常

生成器函数也是一个函数,但是和普通函数有一定的区别:

  • 语法上:在function后加“*”;
  • 生成器函数可以使用“yield”关键字来控制函数的执行流程
  • 生成器函数返回一个生成器
  • 单独的函数调用不会有任何结果,需要使用变量来接收函数返回的生成器。

生成器一般和生成器函数一起使用

简单示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function* foo() {
console.log(1);
// 第一次next()时,返回{value: 1, done: false}
yield 1;
// 此处的n为第二次调用next传入的参数,可当作第一次yield的返回值
// 第二次next()时,返回{value: 2, done: false}
yield 2;
console.log(3);
// 第三次next()时,返回{value: 3, done: false}
yield 3;
// 第四次next()时,返回{value: 'foo', done: true}
return 'foo'
}
const iterator = foo()

console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.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
function* foo(num) {
console.log(num);
// 想要接收函数的参数,只需在函数调用时传入即可,而不是在第一次调用next时传入、
console.log(num);
const count1 = yield 1;
console.log('第一次yield的返回值,由第二次next传入的参数决定', count1);
// 此处的count1为第二次调用next传入的参数,可当作第一次yield的返回值
// next传入的参数会被当做上一次yield的返回值(即第n次yield的参数是第n-1次yield函数的返回值)
console.log(2 * count1);
yield 2;
console.log(3);
const count3 = yield 3;
console.log('第三次yiled的返回值,由第四次next传入的参数决定', count3);
// 第四次next()时,返回{value: 'foo', done: true}
// 因为函数遇到了return关键字,函数执行完毕
return 'foo'
}
const iterator = foo(5)

console.log(iterator.next());
console.log(iterator.next(10));
console.log(iterator.next());
console.log(iterator.next(20));

注意:第一次调用next方法很少传入参数

生成器其他方法

return

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
function* foo() {
const count1 = yield 1;
console.log(count1);
const count2 = yield 2;
return count2;
}


let iterator = foo(1);
console.log(iterator.next());
// 意味着在第一个yield和第二个yield之间加上return语句,提前终止生成器函数代码的执行
console.log(iterator.return('return'));
// { value: 1, done: false }
// { value: 'return', done: true }

// 等同于
function* foo() {
const count1 = yield 1;
console.log(count1);

return count1;
const count2 = yield 2;
return count2;
}
let iterator = foo(1);
console.log(iterator.next());
console.log(iterator.return('return'));
// { value: 1, done: false }
// { value: 'return', done: true }
// 之后再调用next方法均返回
// { value: undefined, done: true}

throw

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function* foo() {
try {
const count1 = yield 1;
console.log(count1);
} catch (error) {
console.log('error', error);
yield '错误捕获到之后继续执行'
}
console.log('代码继续执行');
const count2 = yield 2;
return count2;
}

let iterator = foo();
console.log(iterator.next());
console.log(iterator.throw('iterator throw'));
console.log(iterator.next());

// { value: 1, done: false }
// error iterator throw
// { value: '错误捕获到之后继续执行', done: false }
// 代码继续执行
// { value: 2, done: false }

生成器替代迭代器

使用循环来yield数据

迭代器的目的是返回一个带有next方法的对象

但是,生成器也是一种迭代器;我们可以使用生成器函数直接返回一个生成器,来调用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
function createIterator(arr) {
let index = 0;
return {
next: function () {
return index < arr.length ?
{ value: arr[index++], done: false } :
{ done: true, value: undefined };
}
}
}
let arr = [1, 2, 3]
let iterator = createIterator(arr);
console.log(iterator.next());
console.log(iterator.next());
// 可修改为
function* createIterator(arr) {
for (const item of arr) {
yield item
}
}
let arr = [1, 2, 3, 4, 5];
const iterator = createIterator(arr);
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());

yield*

事实上我们也可以使用给yield*来生产一个可迭代对象;

这个时候相当于是yield的语法糖,只不过会一次迭代这个可迭代对象,每次迭代其中一个值。

(yield* 之后要加上一个可迭代对象)

1
2
3
4
5
6
7
function* createIterator(arr) {
yield* arr
}
let arr = [1, 2, 3, 4, 5];
const iterator = createIterator(arr);
console.log(iterator.next());
console.log(iterator.next());

其他例子

示例1

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
// 创建一个可生成一个范围内数字的迭代器
function createRangeIterator(start, end) {
let index = start;
return {
next: function () {
if (index <= end) {
return { done: false, value: index++ };
} else {
return { done: true, value: undefined };
}
}
};
}

let iterator = createRangeIterator(10, 20);
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());

// 使用yiled修改
function* createRangeIterator(start, end) {
let index = start;
while (index < end) {
yield index++;
}
}

let iterator = createRangeIterator(10, 20);
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());

示例2

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
// 自定义对象的可迭代性
// 新建教室类,该教室的学生可遍历
class Room {
constructor(address, name) {
this.address = address;
this.name = name;
this.students = [];
}
pushStudent(student) {
this.students.push(student);
}
// Room.prototype[Symbol.iterator] = function() {}
[Symbol.iterator]() {
let index = 0;
return {
// 注意使用箭头函数
next: () => {
if (index < this.students.length) {
return {
done: false,
value: this.students[index++]
}
} else {
return {
done: true,
value: undefined
}
}
},
}
}
}
// 使用yield修改[Symbol.iterator]方法
*[Symbol.iterator]() {
let index = 0;
while (index < this.students.length) {
yield this.students[index++];
}
// 或者直接
// yield* this.students
}
let room = new Room('行远楼A105', '软件工程教室');
room.pushStudent('hrm');
room.pushStudent('xcl');
room.pushStudent('dmy');
room.pushStudent('gq');

let iterator = room[Symbol.iterator]();
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
// { done: false, value: 'hrm' }
// { done: false, value: 'xcl' }
// { done: false, value: 'dmy' }
// { done: false, value: 'gq' }
// { done: true, value: undefined }
for (let student of room) {
console.log(student);
}

生成器向async_await的过渡

异步代码的处理方案

现有一个场景,我们有一个网络请求函数,需要用这个网路函数发送三次网络请求,第二次的请求参数是第一次请求的结果

原始方案(回调函数)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function requestData(params, successCallback, failCallback) {
// setTimeout模拟网络请求
setTimeout(function () {
// 成功的回调,将传入的参数 + 1
successCallback(params + 1);
}, 100)
}
// 我们暂定网络请求均成功,此处不传失败回调
requestData(1, (res) => {
requestData(res, (res2) => {
requestData(res2, (res3) => {
console.log(res3);
})
})
})

改进

1
2
3
4
5
6
7
8
// 将函数改进为返回一个promise
function requestData(params) {
return new Promise((reslove, reject) => {
setTimeout(function () {
reslove(params + 1)
}, 100)
})
}

promise.then

1
2
3
4
5
6
7
8
requestData(1).then((res) => {
requestData(res).then((res2) => {
requestData(res2).then((res3) => {
console.log(res3);
})
})
})
// 看上去也是回调地狱,没有什么改进哈哈

promise.then返回新的promise

1
2
3
4
5
6
7
requestData(1).then((res) => {
return requestData(res)
}).then((res2) => {
return requestData(res2)
}).then((res3) => {
console.log(res3);
})

promise + generator

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
function requestData(params) {
return new Promise((reslove, reject) => {
setTimeout(function () {
reslove(params + 1)
}, 100)
})
}

function* getData() {
const res = yield requestData(1);
const res2 = yield requestData(res);
yield requestData(res2);
}

// 拿到生成器
const genetator = getData();
// 第一次调用next方法,拿到第一次yield之后的值:requestData(1)(一个promise),并且把该值赋值给value(value: promise)
genetator.next().value.then((res) => {
// 第二次调用next方法,next方法传入的参数是第一次yield requestData(1)的返回值,即2(res)
// 第二次调用next方法,拿到第二次yield 之后的值:requestData(res)的返回值(一个promise),并且该值赋值给value(value: promise)
genetator.next(res).value.then((res2) => {
// 第三次调用next方法,next方法传入的参数是第二次yield requestData(res)的返回值,即3
// 第三次调用next方法,拿到第三次yield requestData(res2)的返回值(一个promise),并且把该值赋值给value(value: promise)
// 最后的promise.then拿到最终的执行结果
genetator.next(res2).value.then((res3) => {
console.log(res3);
})
})
})
// 有点绕.....
promise+generator函数自动化执行封装
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
function requestData(params) {
return new Promise((reslove, reject) => {
setTimeout(function () {
reslove(params + 1)
}, 100)
})
}

function* getData() {
const res = yield requestData(1);
const res2 = yield requestData(res);
const res3 = yield requestData(res2);
console.log(res3);
}

function execGenerator(genetatorFn) {
// 拿到生成器(迭代器 )
let generator = genetatorFn();
function exec(res) {
const result = generator.next(res);
if (result.done) {
return result.value;
}
result.value.then(res => {
exec(res)
})
}
return exec()
}
execGenerator(getData);

参看npm依赖co

开发者:TJ(TJ Holowaychuk)

该作者对前端的贡献非常大,例如:

  • npm三方依赖

    • n(node版本管理工具)
    • commander(vue-cli中使用)
  • express

  • koa(egg是基于koa的)

co的使用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
const co = require('co');
function requestData(params) {
return new Promise((reslove, reject) => {
setTimeout(function () {
reslove(params + 1)
}, 100)
})
}

function* getData() {
const res = yield requestData(1);
const res2 = yield requestData(res);
const res3 = yield requestData(res2);
console.log(res3);
return res3;
}
co(getData).then((res) => {
console.log(res);
});

async_await

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function requestData(params) {
return new Promise((reslove, reject) => {
setTimeout(function () {
reslove(params + 1)
}, 100)
})
}


async function getData() {
let res1 = await requestData(1);
let res2 = await requestData(res1);
let res3 = await requestData(res2);
return res3
}
getData().then((res) => {
console.log(res);
})

async_await

async:

异步,用来声明一个异步函数,异步函数的返回值一定是一个promise

这个promise的then方法调用时机:异步函数返回了值

sync:与async对立,表示同步

异步函数的返回值

返回一个普通值

js会将返回的普通值用prosmie进行包裹

1
2
3
4
5
6
7
async function foo() {
return 'foo'
}
// res的值就是return的值
foo().then((res) => {
console.log(res);
})

返回一个实现了then方法的对象

如果这个返回了一个thenable对象(即该对象实现了then方法),promise.then方法的相关回调由该对象的then方法决定

1
2
3
4
5
6
7
8
9
10
11
12
13
14
async function foo() {
return {
then(resolve, reject) {
resolve('obj then method resolve');
// reject('obj then method reject');
}
}
}

foo().then((res) => {
console.log(res);
}, (err) => {
console.log(err);
})

返回一个新的promise

返回一个新的promise,会进行状态移交,会根据返回的新的promise进行相关回调

1
2
3
4
5
6
7
8
9
10
11
async function foo() {
return new Promise((resolve, reject) => {
resolve('new promise resolve')
})
}

foo().then((res) => {
console.log(res);
}, (err) => {
console.log(err);
})

async函数抛出异常

异步函数抛出的异常会被作为返回的prosmise的err值,用catch、或者用then的第二个回调来捕获该错误

1
2
3
4
5
6
7
8
9
10
11
12
13
14
async function foo() {
throw new Error('error')
}

foo().then((res) => {
console.log(res);
}, (err) => {
console.log('then的第二个回调捕获', err);
})

// 或者
foo().catch((err) => {
console.log('catch捕获', err);
})

await

在异步函数内部可以使用await关键字

await之后跟表达式(基本使用)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function foo() {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve('result')
}, 500)
})
}

async function getData() {
let res1 = await foo();
let res2 = await foo();
// 在得到res1,res2之前,之后的代码不会执行
console.log('拿到res1,res2的值', res1, res2);
return [res1, res2];
}

getData().then(data => {
console.log(data);
})

实际上,上述的getData函数修改为普通函数时,是这样的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function foo() {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve('result')
}, 500)
})
}
function getData() {
return new Promise((resolve, reject) => {
foo().then((res1) => {
foo().then((res2) => {
resolve([res1, res2])
})
})
})
}

getData().then(data => {
console.log(data);
})

async配饰await的使用,使得异步代码看起来时同步代码的样子,实则是异步代码的一种语法糖

await跟新的promise

这种情况适用于await之后跟表达式

1
2
3
4
5
6
7
8
9
10
11
async function foo() {
let res = await new Promise((resolve, reject) => {
resolve('await new promise resolve')
})
return res
}

foo().then((res) => {
console.log(res);
})
// 'await new promise resolve'

await之后跟普通值

如果await之后跟上是一个普通值,会立即返回

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
async function foo() {
let res = await 1;
return res
}

foo().then((res) => {
console.log(res);
})
// 相当于
async function foo() {
let res = await new Promise((resolve, reject) => {
resolve(1)
})
return res
}

foo().then((res) => {
console.log(res);
})
// 1

await之后跟thenable对象

1
2
3
4
5
6
7
8
9
10
11
12
13
async function foo() {
let res = await {
then(resolve, reject) {
resolve('await obj then resolve')
}
}
return res
}

foo().then((res) => {
console.log(res);
})
// await obj then resolve

await:reject值

以上情况,均resolve了值,如果reject之后,会有什么情况呢?

此时需要使用catch来进行捕获

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function requestData(params) {
return new Promise((reslove, reject) => {
setTimeout(function () {
reject('error')
}, 100)
})
}

async function getData() {
let res1 = await requestData();
return res1;
}

getData().then(undefined, (err) => {
console.log(err);
})

或者

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function requestData(params) {
return new Promise((reslove, reject) => {
setTimeout(function () {
reject('error')
}, 100)
})
}

async function getData() {
try {
let res = await requestData();
console.log(res);
} catch (error) {
console.log(error);
}
}
getData()