熟悉ES6语法的同窗们确定对Generator(生成器)函数不陌生,这是一个化异步为同步的利器。 栗子:javascript
function* abc() {
let count = 0;
while(true) {
let msg = yield ++count;
console.log(msg);
}
}
let iter = abc();
console.log(iter.next().value);
// 1
console.log(iter.next('abc').value);
// 'abc'
// 2
复制代码
首先,咱们先简单回顾一下JS的运行规则:java
那么,Generator函数是如何进行异步化为同步操做的呢? 实质上很简单,* 和 yield 是一个标识符,在浏览器进行软编译的时候,遇到这两个符号,自动进行了代码转换:git
// 异步函数
function asy() {
$.ajax({
url: 'test.txt',
dataType: 'text',
success() {
console.log("我是异步代码");
}
})
}
function* gener() {
let asy = yield asy();
yield console.log("我是同步代码");
}
let it = gener().next();
it.then(function() {
it.next();
})
// 我是异步代码
// 我是同步代码
复制代码
// 浏览器编译以后
function gener() {
// let asy = yield asy(); 替换为
$.ajax({
url: 'test.txt',
dataType: 'text',
success() {
console.log("我是异步代码");
// next 以后执行如下
console.log("我是同步代码");
}
})
// yield console.log("我是同步代码");
}
复制代码
整个过程相似于,浏览器遇到标识符 * 以后,就明白这个函数是生成器函数,一旦遇到 yield 标识符,就会将之后的函数放入此异步函数以内,待异步返回结果后再进行执行。github
更深一步,从内存上来说:ajax
普通函数在被调用时,JS 引擎会建立一个栈帧,在里面准备好局部变量、函数参数、临时值、代码执行的位置(也就是说这个函数的第一行对应到代码区里的第几行机器码),在当前栈帧里设置好返回位置,而后将新帧压入栈顶。待函数执行结束后,这个栈帧将被弹出栈而后销毁,返回值会被传给上一个栈帧。npm
当执行到 yield 语句时,Generator 的栈帧一样会被弹出栈外,但Generator在这里耍了个花招——它在堆里保存了栈帧的引用(或拷贝)!这样当 it.next 方法被调用时,JS引擎便不会从新建立一个栈帧,而是把堆里的栈帧直接入栈。由于栈帧里保存了函数执行所需的所有上下文以及当前执行的位置,因此当这一切都被恢复如初之时,就好像程序从本来暂停的地方继续向前执行了。浏览器
而由于每次 yield 和 it.next 都对应一次出栈和入栈,因此能够直接利用已有的栈机制,实现值的传出和传入。框架
至此,Generator 的魔力已经揭开。异步
Promise的用法你们应该都很熟悉:函数
let pr = new Promise(function(resolve, reject) {
setTimeout(function() {
resolve("成功执行啦");
}, 2000)
})
pr.then(function(data) {
console.log(data); // 成功执行啦
})
复制代码
那么 Promise 是如何实现异步加载的呢?
Promise 并无你们想的那么神秘,其本质就是一个状态机。
想要实现一个土生土长的 Promise 其实很简单,状态机,咱们须要几个参数:
下面就手动实现一个 Promise
class Promise1 {
constructor(fn) {
// 执行队列
this.__watchList = [];
// 成功结果
this.__success_res = null;
// 失败结果
this.__error_res = null;
// 状态
this.__status = "";
fn((...args) => {
// 保存成功数据
this.__success_res = args;
// 状态改成成功
this.__status = "success";
// 若为异步则回头执行then成功方法
this.__watchList.forEach(element => {
element.fn1(...args);
});
}, (...args) => {
// 保存失败数据
this.__error_res = args;
// 状态改成失败
this.__status = "error";
// 若为异步则回头执行then失败方法
this.__watchList.forEach(element => {
element.fn2(...args);
});
});
}
// then 函数
then(fn1, fn2) {
if (this.__status === "success") {
fn1(...this.__success_res);
} else if (this.__status === "error") {
fn2(...this.__error_res);
} else {
this.__watchList.push({
fn1,
fn2
})
}
}
}
复制代码
这样就简单实现了 Promise 的功能,在使用上和JS的 Promise 并没有其余区别,若想实现 Promise.all 方法,则只须要进行小小的迭代:
Promise1.all = function(arr) {
// 存放结果集
let result = [];
return Promise1(function(resolve, reject) {
let i = 0;
// 进行迭代执行
function next() {
arr[i].then(function(res) {
// 存放每一个方法的返回值
result.push(res);
i++;
// 若所有执行完
if (i === result.length) {
// 执行then回调
resolve(result);
} else {
// 继续迭代
next();
}
}, reject)
}
})
}
复制代码
至此,Generator 和 Promise 都已解析完成。
最后很差意思推广一下我基于 Taro
框架写的组件库:MP-ColorUI。
能够顺手 star 一下我就很开心啦,谢谢你们。