本文主要讲解生成器,需要先了解迭代器Iterator,对迭代器不了解的可以去看下对象如何部署迭代器的那篇文章,对迭代器有个初步的认识,部署了迭代器,就可以使用 for ... of 遍历对象啦,棒棒哒!
Generator生成器函数的作用:
可以基于返回的iterator (迭代器对象) ,基于其next方法,控制函数体中的代码,一步步的去执行!!+ 每一次执行next,控制函数体中的代码开始执行,直到遇到 yield 则等待!
done: false
value:yield后面的值
当遇到函数体中的return,或者已经执行到函数最未尾的位置了
done:true
value:函数的返回值或者undefined
「把它当做一个实例 __proto__」
普通函数是 Function 的实例,普通函数.__proto__===Function.prototype
生成器函数是 GeneratorFunction 的实例
生成器函数.__proto__===GeneratorFunction.prototype
GeneratorFunction.prototype.__proto__===Function.prototype
({}).toString.call(生成器函数) => "[object GeneratorFunction]"
「把它作为一个构造函数 prototype」
生成器函数不能被new执行 Uncaught TypeError: func is not a constructor
当做普通函数执行,返回的结果就是生成器函数的一个实例
itor.__proto__ -> func.prototype「空对象,没有constructor」 -> Generator.prototype「constructor:GeneratorFunction」{next/return/throw/Symbol(Symbol.toStringTag): "Generator"} -> 一个具备迭代器规范的对象「Symbol(Symbol.iterator)」 -> Object.prototype
声明generator函数:function后面加一个*
function* fn() {
console.log(this);
return 10;
}
fn.prototype.query = function () {};
let gen = fn();
console.log(gen);
// gen.__proto__ -> fn.prototype「query」 -> GeneratorFunction.prototype「next/return/throw/Symbol.toStringTag」-> xxx.prototype「Symbol.iterator」 -> Object.prototype
console.log(typeof fn); //->"function"
console.log(fn instanceof Function); //->true
function* generator() {
console.log('A');
yield 10;
console.log('B');
yield 20;
console.log('C');
yield 30;
console.log('D');
return 100;
}
let itor = generator();
console.log(itor.next()); //->{value:10,done:false}
console.log(itor.next()); //->{value:20,done:false}
console.log(itor.next()); //->{value:30,done:false}
console.log(itor.next()); //->{value:100,done:true}
console.log(itor.next()); //->{value:undefined,done:true}
function* generator() {
console.log('A');
yield 10;
console.log('B');
yield 20;
console.log('C');
return 30;
}
let itor = generator();
console.log(itor.next()); //->{value:10,done:false}
console.log(itor.return('@return')); //->{value:"@return",done:true}
console.log(itor.throw('@throw')); // 手动抛出异常,生成器函数中的代码都不会在执行了
console.log(itor.next()); //->{value:undefined,done:true}
// 每一次执行next的传递的值,是作为上一次yeild的返回值处理的
itor.next(N): 每一次执行next方法,传递的值会作为上一个yield的返回值所以第一次执行next方法,传递的值是没有用的,因为在它之前没有yield
function* generator() {
let x1 = yield 10;
console.log(x1); // @2
let x2 = yield 20;
console.log(x2); // @3
return 30;
}
let itor = generator();
itor.next('@1');
itor.next('@2');
itor.next('@3');
// yeild* 后面跟着一个新的iterator,后期执行到这的时候,会进入到新的generator中执行
function* generator1() {
yield 10;
yield 20;
}
function* generator2() {
yield 10;
yield* generator1(); // yield* 支持让我们进入另一个生成器函数中一步步执行
yield 20;
}
let itor = generator2();
console.log(itor.next()); //value:10 done:false
console.log(itor.next()); //value:10 done:false
console.log(itor.next()); //value:20 done:false
console.log(itor.next()); //value:20 done:false
console.log(itor.next()); //value:undefined done:true
const delay = (interval = 1000) => {
return new Promise(resolve => {
setTimeout(() => {
resolve(`@@${interval}`);
}, interval);
});
};
通过以上想必对生成器有个初步的认识了,建议初学者自己敲打代码熟悉一下
下面我们将讲解生成器如何实现 async...await
现在我们有个需求:串行请求,有三个请求「请求需要的时间分别是 1000/2000/3000」?
先写一个延迟函数,模拟异步请求
const delay = (interval = 1000) => {
return new Promise(resolve => {
setTimeout(() => {
resolve(`@@${interval}`);
}, interval);
});
};
这个延迟函数如果看不懂的话,下面的内容就不要看了,穿上黄马褂吧
1.通过 Promise 实现串行请求
delay(1000)
.then(value => {
console.log('第一个请求成功:', value);
return delay(2000);
})
.then(value => {
console.log('第二个请求成功:', value);
return delay(3000);
})
.then(value => {
console.log('第三个请求成功:', value);
})
.catch(reason => {
console.log('任何请求失败,都执行这里:', reason);
});
2.通过 async...await 实现串行请求
(async () => {
try {
let value = await delay(1000);
console.log('第一个请求成功:', value);
value = await delay(2000);
console.log('第二个请求成功:', value);
value = await delay(3000);
console.log('第三个请求成功:', value);
} catch (reason) {
console.log('任何请求失败,都执行这里:', reason);
}
})();
3.通过生成器函数实现串行请求
const handle = function* handle() {
let value = yield delay(1000);
console.log('第一个请求成功:', value);
value = yield delay(2000);
console.log('第二个请求成功:', value);
value = yield delay(3000);
console.log('第三个请求成功:', value);
};
let iter = handle() // 获取迭代器对象
/* done:是否执行完毕 value:获取的是每一次yield后面的值->Promise实例 */
let { value, done } = iter.next() // {value: Promise, done: false}
value.then(res => {
console.log('res', res);
// res 第一次请求成功的结果 @@1000
let { value, done } = iter.next(res)
value.then(res => {
// res 第二次请求成功的结果 @@2000
let { value, done } = iter.next(res)
value.then(res => {
// res 第三次请求成功的结果 @@1000
let { value, done } = iter.next(res)
})
})
})
虽然生成器函数也可以实现,但是每次调用都需要自己手动调用next方法,而且又有回调地狱的情况,使用起来一点都不友好,那怎么解决这个问题呢?
我们封装一个函数,实现递归调用next方法
function AsyncFunction(generator, ...params) {
let iter = generator(...params);
// 基于递归的方法,通知Generator函数中的代码逐一执行
const next = x => {
let { done, value } = itor.next(x);
if (done) return;
if (!(value instanceof Promise)) value = Promise.resolve(value);
// value.then(()=>{next(x)});
value.then(next);
};
next();
};
AsyncFunction(handle);
这个时候我们使用封装的AsyncFunction函数再去实现串行请求
function* handle() {
let value = yield delay(1000);
console.log('第一个请求成功:', value);
value = yield delay(2000);
console.log('第二个请求成功:', value);
value = yield delay(3000);
console.log('第三个请求成功:', value);
};
AsyncFunction(handle) // 这样就ok啦,是不是很方便
此时并不能看出来这个封装函数和async...await有啥相似之处,下面且看另一种写法
AsyncFunction(function* handle() {
let value = yield delay(1000);
console.log('第一个请求成功:', value);
value = yield delay(2000);
console.log('第二个请求成功:', value);
value = yield delay(3000);
console.log('第三个请求成功:', value);
});
(async () => {
try {
let value = await delay(1000);
console.log('第一个请求成功:', value);
value = await delay(2000);
console.log('第二个请求成功:', value);
value = await delay(3000);
console.log('第三个请求成功:', value);
} catch (reason) {
console.log('任何请求失败,都执行这里:', reason);
}
})()
此时再看AsyncFunction是不是相当于async ,yield 相当于 await