在解释generator之前,我们先来说下同步/异步以及Promise ,因为generator和Promise有许多类似的地方,Promise是为了解决回调地狱,而Generator 是为了应对异步问题。
1.同步与异步
众所周知,JS是一个单线程语言,同一个时间点只能做一件事。因而所有任务需要排队,只有前面任务结束,才会执行后面的任务。这样带来的问题:如果JS执行时间长,致使页面渲染不连贯,页面卡顿体验不好。为解决这个问题,HTML5出台了Web Worker标准,支持JavaScript脚本生成多个线程,这就是JS中的同步和异步。异步任务又分为宏任务(javascript代码,setTimeout,setinterval,setimmediate,io操作,ui render,异步ajax)和微任务(promise then,catch,finnaly,async/await , process.nextTick, Object.observe, mutationobserve),其中异步任务属于耗时的任务。
promise经典面试题:(微任务优先执行)
setTimeout(function () {
console.log('1');
})
new Promise(function (resolve) {
console.log('2');
resolve();
}).then(function () {
console.log('3');
})
console.log('4');
//打印顺序 2 4 3 1
执行顺序:
let axios = require('axios');
let fs = require('fs');
console.log('begin');
fs.readFile('1.txt',(err,data)=>{
console.log('fs');
});
axios.get('https://api.muxiaoguo.cn/api/xiaohua?api_key=fd3270a0a9833e20').then(res=>{
console.log('axios');
});
setTimeout(()=>{
console.log('setTimeout')
},0);
setImmediate(()=>{
console.log('setImmediate');
});
(async function (){
console.log('async')
})();
console.log('end');
//执行结果:begin async end setTimeout setImmediate fs axios
当setTimeout延迟时间也是0毫秒时,谁在前面就先执行谁。此外如果setTimeout延迟时间不是0毫秒,它的执行顺序会在 i/o 操作之后。
2.什么是generator
对于普通函数而言,它会立即执行,而Generator 函数特性是可以停下,不会立即执行,正是这样的特性,它能很好处理异步问题。
定义一个 Generator 函数和定义一个普通函数是类似的,不同之处在于它在 function 和函数名之间有一个*号。
Generator 函数返回是一个迭代器对象,需要通过 xxx.next 方法来完成执行。在调用 generator 函数时,它只是进行实例化工作,它没有让函数体里面的代码执行,需要通过 next 方法来让它执行,如下面:
function* gen() {
console.log(1)
}
// 定义迭代器对象
const iterator = gen()
iterator.next() // 执行这一局代码,1才会被打印
当 next 方法执行时遇到了yield时 会停止,直到你再次调用 next 方法。如下:
function* genar() {
yield 1
console.log('A')
yield 2
console.log('B')
yield 3
console.log('C')
return 4
}
// 定义迭代器对象
const iterator = genar()
iterator.next() // 执行 genar 函数,打印为空,遇到 yield 1 停止执行
iterator.next() // 继续执行函数,打印 A,遇到 yield 2 停止执行
iterator.next() // 继续执行函数,打印 B,遇到 yield 3 停止执行
iterator.next() // 继续执行函数,打印 C
3.generator能做什么
我们说 Generator 能够把异步变同步,是因为 Generator 函数中我们只需要写同步代码就可以,真正执行异步操作的是迭代器对象。
在复杂的业务逻辑中,大量使用迭代器对象来执行异步操作,会使得代码变得很不优雅,于是 ES7 中就推出了 async await 方案来实现异步变同步。在 async await 方案中可以只书写同步代码,真正的异步操作被封装在底层,这样写使得代码变优雅了很多。
4.generator的应用场景
使用generator来完成异步网络请求,它还是要利用到promise, 优雅的写法,当有多个网络请求时,异步操作部分的代码会变得非常复杂,所以我们可以通过 co 库中的迭代函数来改写一下:
// 使用generator来完成异步网络请求,利用promise
// 模拟网络请求
function request(num) {
return new Promise((resolve, reject) => {
return setTimeout(() => {
resolve(++num)
}, 1000);
})
}
// generator函数中的代码,发起的网络请求它就类似于同步写法
function* gen(num) {
// yield右侧是一个promise对象
let r1 = yield request(10)
console.log('r1', r1);
let r2 = yield request(r1)
console.log('r2', r2);
let r3 = yield request(r2)
console.log('r3', r3);
let r4 = yield request(r3)
console.log('r4', r4);
let r5 = yield 'ok'
console.log('r5', r5);
}
// 通过co库实现
function co(generator, ...params) {
const iterator = gen(...params)
// 迭代函数
const next = n => {
let { value, done } = iterator.next(n)
// 判断一下value它是一个promise对象,如果不是promise对象则需要手动转为promise对象,或抛异常
if (value != undefined && typeof value.then != "function") {
throw new Error('必须为promise对象')
// value = Promise.resolve(value)
}
if (done) return;
// value.then(ret => next(ret))
value.then(next)
}
next(0)
}
co(gen, 100)