文章目录
- 前言
- 一、async await实现
- 二、Generator实现
- 三、Generator函数
- 1、什么是Generator
- 2.Generator的特点
- 3.Generator的执行
- 四、yield表达式
- 1、什么是yield
- 2、yield注意点
- 五、Iterator对象
- 五、next方法的参数
- 总结
前言
问题发生在一个下午,当我正在实现Promise.all()方法的时候(有兴趣的可以去我另一篇博客看一看:深入透析Promise的all和race方法),发现for循环里的函数并不是同步执行的,那么我如果想让它同步执行该怎么办呢?
大家先来看一下这道题:
for(let i = 0;i < 5;i++) {
if(i === 1) {
setTimeout(() => {
console.log(i);
}, 0);
}else {
console.log(i);
}
}
执行结果:
这里便是宏任务与微任务的问题啦,不懂的同学可以去看一下事件循环知识哦!
我们发现1被推到了最后才去执行,那么我就想让他同步执行,也就是按顺序执行该怎么办呢?
- 思路一:给函数包一层Promise,然后用async await使其同步执行。(这样还得给它而外包裹一层promise,不是多次一举吗?)
- 思路二:使用Generator
话不多说,让我们一起来看一看吧
一、async await实现
async await和promise的内容本文就不具体讲了,直接上代码!
const promise= (i) => new Promise((resolve,reject) => {
setTimeout(() => {
// 此处resolve目的是为了让promise结束,否则await将一直等待
resolve()
console.log(i);
}, 0);
})
~(async (resolve,reject) => {
for(let i = 0;i < 5;i++) {
if(i === 1){
await promise(i)
}else {
console.log(i)
}
}
})()
让我们分析一下代码,由于await后面只能接promise才生效,我们为了让setTimeout不被排在最后执行,我们使用promise给console.log包了一层,让它成为同步执行的代码。
二、Generator实现
先看一下实现的过程,下面我们将介绍Generator和yield的一些使用
代码实现:
function* test() {
for(let i = 0;i < 5;i++) {
yield i
}
}
const num = test()
for(let i of test()) {
if(i === 1) {
setTimeout(() => {
console.log(num.next().value);
}, 0);
}else{
console.log(num.next().value);
}
}
代码的意思呢就是:将我们需要的参数封装generator的内部对象(本文中的1-5),使用num接收generator返回的对象。
返回的对象有一点像一个链表,让我们必须执行完当前状态才能去执行下一个。
所以在for循环里,很严格的让数字按顺序执行。
三、Generator函数
1、什么是Generator
- Generator 函数是 ES6 提供的一种异步编程解决方案
- Generator 函数是一个状态机,封装了多个内部状态。
- 执行 Generator 函数会返回一个遍历器对象,可以依次遍历 Generator 函数内部的每一个状态。
2.Generator的特点
- function关键字与函数名之间有一个星号(es6并没有严格规定星号位置,可写在function和函数名之间的任意处)
- 函数体内部使用yield表达式,定义不同的内部状态
function* test() {
yield 1
yield 2
yield 3
return 4
}
const num = test()
上述方法中使用num作为接收调用test()返回的对象,yield后面为封锁的状态,return后面为最后结束的状态。
3.Generator的执行
上面我们只是定义了一个generator,但是我们要怎样去使用它里面的状态值呢?
大家有没有发现,generator对象特别像一个链表,我们也可以像遍历链表的方式去遍历generator。
function* test() {
yield 1
yield 2
return 3
}
const num = test()
num.next() // {{ value: 1, done: false }}
num.next() // {{ value: 2, done: false }}
num.next() // {{ value: 3, done: true }}
num.next() // {{ value: undefined, done: true }}
让我们来分析一下调用步骤:
- 第一次调用,Generator 函数开始执行,直到遇到第一个yield表达式为止。next方法返回一个对象,它的value属性就是当前yield表达式的值1,done属性的值false,表示遍历还没有结束。
- 第二次调用,Generator 函数从上次yield表达式停下的地方,一直执行到下一个yield表达式。next方法返回的对象的value属性就是当前yield表达式的值world,done属性的值false,表示遍历还没有结束。
- 第三次调用,Generator 函数从上次yield表达式停下的地方,一直执行到return语句(如果没有return语句,就执行到函数结束)。next方法返回的对象的value属性,就是紧跟在return语句后面的表达式的值(如果没有return语句,则value属性的值为undefined),done属性的值true,表示遍历已经结束。
- 第四次调用,此时 Generator 函数已经运行完毕,next方法返回对象的value属性为undefined,done属性为true。以后再调用next方法,返回的都是这个值。
四、yield表达式
1、什么是yield
- 由于 Generator 函数返回的遍历器对象,只有调用next方法才会遍历下一个内部状态,所以其实提供了一种可以暂停执行的函数。yield表达式就是暂停标志。
2、yield注意点
yield表达式后面的表达式,只有当调用next方法、内部指针指向该语句时才会执行,因此等于为 JavaScript 提供了手动的“惰性求值”(Lazy Evaluation)的语法功能。
例如:
function* test() {
yield 1 + 2
}
上面代码中,yield后面的表达式123 + 456,不会立即求值,只会在next方法将指针移到这一句时,才会求值。
五、Iterator对象
Generator函数执行时生成的是一个iterator对象,且此时不再需要调用next方法。所以它可以使用Iterator接口。
例如:
function* numbers () {
yield 1
yield 2
return 3
yield 4
}
// 扩展运算符
[...numbers()] // [1, 2]
// Array.from 方法
Array.from(numbers()) // [1, 2]
// 解构赋值
let [x, y] = numbers();
x // 1
y // 2
// for...of 循环
for (let n of numbers()) {
console.log(n)
}
// 1
// 2
五、next方法的参数
yield表达式本身没有返回值,或者说总是返回undefined。next方法可以带一个参数,该参数就会被当作上一个yield表达式的返回值。
比如:
function* foo(x) {
var y = 2 * (yield (x + 1));
var z = yield (y / 3);
return (x + y + z);
}
var a = foo(5);
a.next() // Object{value:6, done:false}
a.next() // Object{value:NaN, done:false}
a.next() // Object{value:NaN, done:true}
var b = foo(5);
b.next() // { value:6, done:false }
b.next(12) // { value:8, done:false }
b.next(13) // { value:42, done:true }
我们首先分析第一个foo(5):
- 第一个next到达第一个yield,所以value为yield后表达式,即5+1=6
- 第二个next到达第二个yield,此时y为NaN,所以y / 3为NaN,value为NaN
- 第三个next到达return,此时y和z为NaN,所以value为NaN
我们再来分析第二个foo(5):
- 第一个next到达第一个yield,所以value为yield后表达式,即5+1=6
- 第二个next携带12作为上一个yield,即第一个yield的参数,所以此时y = 2 * 12 = 24,此时的yield = y / 3 = 8
- 第三个next携带13作为上一个yield,即第二个yield的参数,所以此时z = 13,此时的yield = x + y + z = 42
总结
文章只是简单介绍了Generator和yield的基本用法和注意事项,如果有兴趣详细了解的同学可以去学习一下哦!