generator---生成器,写法和普通函数(用function关键字声明)基本一样,只是在函数名前增加了“*”号,它可以返回多次(除了最终的返回,中间的每次返回都需要“yield”关键字来标识),并且在每个阶段都可以传参,因此它可以用来优雅地替代异步回调式的代码。
先来看一个简单的generator
function * simple(){ // * 可以紧跟function,也可跟着函数名,还可以放在他俩中间
yield 1;
yield 2;
}
//函数simple执行后,和普通的函数不一样,它会返回一个iterator(迭代器)
let it = simple(); //这是一个迭代器。它拥有一个next方法,顾名思义,用来执行下一步。
console.log(it.next()); // {value: 1, done: false} value:这一步得到的值,done: 是否结束
console.log(it.next()); // {value: 2, done: false} 还没有走到函数的return语句时,done总是false
// 当generator函数没有return语句时,就当它 return undefined
console.log(it.next()); // {value: undefined, done: true}
每当函数体中碰到一个“yield”时,函数就会停止,使用迭代器的next方法,才能让它进行下一步。
next方法可以接收参数,用来给这个方法的返回值赋值
function * abc(){
let a = yield 1;
console.log(a);
let b = yield 2;
console.log(b)
}

以上两处next都没有传参,因此赋值时,a和b拿到的都是undefined。还有一个点需要注意,第一次next时传参时没有必要的,因为没有变量(内存)来接收它
现在,我们来模拟一段异步代码
let fs = require("fs");
//这个函数的目的是想拿到文件的内容(假设文件名是未知,从服务器请求来的)
function* getData() {
try {
let fileName = yield new Promise((resolve, reject) => {
setTimeout(() => {
resolve("name.txt"); // 假设有这么一个文件
}, 1500);
});
let data = yield new Promise((resolve, reject) => {
fs.readFile(fileName, "utf8", (err, data) => {
resolve(data);
});
});
return data;
} catch (ex) {
console.log(ex);
}
}
let it = getData(); //生成迭代器
let { value } = it.next(); // 遇到第一个yield,停止,返回一个对象{value: promise, done: false}
value.then(data => {
let { value } = it.next(data); // 将promise成功的返回结果赋给fileName
value.then(data => {
let result = it.next(data); // 读完文件后,将内容赋值给data
console.log(result);
return result;
});
});
以上,如果层级太多还是会出现.then,.then的回调,如你所见,其实它内部都是重复的操作。有一个库会帮我们完成这些繁琐的过程 -----co
首先需要安装它,并且require
let co = require("co");
//他支持promise(thenable)的写法
//以上代码,可以这样简化
co(getData()).then(data => { // 哇塞,清爽了很多,是不是?
console.log(data)
})
知其然,知其所以然,因此,接下来,我们来实现一个co函数
function co(it) {
//接收一个迭代器
return new Promise((resolve, reject) => {
// 他可以then,因此返回promise
function next(val) {
let { value, done } = it.next(val);
if (done) {
//结束, 将结果resolve
resolve(value);
} else {
Promise.resolve(value).then(data => {
// 首先,将value转为promise。 然后将成功的结果赋值,作为下次next的依赖
next(data);
}, (err) => {
it.throw(err); // 如果外层有try-catch,这样写,可以被捕获到。如果用reject,那么错误将被co函数的catch接收到
});
}
}
next();
});
}
async和await是以上的语法糖,将getData函数改为如下:
async function getData() {
try {
let fileName = await new Promise((resolve, reject) => {
setTimeout(() => {
resolve("name.txt"); // 假设有这么一个文件
}, 1500);
});
let data = await new Promise((resolve, reject) => {
fs.readFile(fileName, "utf8", (err, data) => {
resolve(data);
});
});
console.log(data);
return data;
} catch (ex) {
console.log(ex);
}
}
getData(); //直接获取到结果了,不用再手动地next了。
接下来,我们来模拟一个依赖调用,差不多是这个样子:

从上至下,依次命名为ball1,2,3
function move(el, target) {
return new Promise((resolve, reject) => {
let idx = 0;
function step() {
idx += 3;
if (idx < target) {
el.style.left = idx + 'px';
requestAnimationFrame(step) // 原生的动画API
} else {
resolve(target + 100)
}
}
step()
})
}
//基于之前说的async/await,可以这样
async function m(){
let target = await move($('.ball1'), 200);
let target1 = await move($('.ball2'), target);
let target2 = await move($('.ball2'), target1);
return target2; // 这行无关紧要,如果不写,那么m的then函数中的成功参数就是undefined
}
m().then(data => { //这个data就是m方法执行完毕的返回值
console.log(data)
alert`成功`
})
以上, 用async/await完成了一个依赖调用的例子。但是,像target1,target2这两个变量,我是为了使上下强关联才用到的,如果上一步与下一步的关系只有回调 (1走完,然后走2,没有值得依赖关系),那么这些变量可以不用,即:
async function m(){
await move($('.ball1'), 200);
await move($('.ball2'), 200);
await move($('.ball2'), 200);
}
网友评论