~
Promise 篇
本文皆在实现Promise的所有方法,代码均测试可运行,编写于2019年11月17日
GitHub仓库更有自己实现的webpack、mini-react、redux、react-redux、websocket , Electron跨平台桌面端、React-native移动端开源项目等
仓库地址:
https://github.com/JinJieTan
回顾Promise:
new Promise中的构造器函数同步执行
then的回调函数以微任务的形式执行
调用resolve,reject后,状态不能再被改变,传递的值也是
每次.then后返回的还是一个promise
promise可以嵌套
其余后面会谈到
正式开始:
乞丐版:
编写逻辑:
1.Promise被 new 调用
2.每次失败或者成功需要指定回调函数,并且可以传递值
3.Promise拥有.then方法
上面代码有个问题,状态改变应该是异步的,.then应该是微任务形式执行
异步改变状态并且支持三种状态版本:
编写思路
状态只能由Pending改变,而且只能改变一次
异步改变状态,异步的执行.then
支持链式调用
写到这里,需要暂停,捋一捋思路。
万事开头难,其实编写一个Promise是非常简单的事情,看懂上面这两段代码,然后彻底搞清楚这三点,再往下看。
Promise的构造器函数是同步执行
resolve、reject的调用是同步调用,异步执行.例如resolve()是同步调用了resolve这个函数,但是resolve函数内部的代码是异步的。---即异步改变状态,异步执行.then
new Promise((resolve,reject)=>{
console.log('这里是同步执行')
resolve(‘resolve函数内部是异步执行’)
}).then(()=>{
console.log('这里等resolve函数内部异步执行,状态改变以后再执行')
})
彻底搞懂上面的这个例子和两句话,然后你就可以往下看了,其实下面也都是一些重复或者细节处理的工作
支持.then的链式调用
想支持链式操作,其实很简单,首先存储回调时要改为使用数组
self.onFulfilledCallbacks = [];
self.onRejectedCallbacks = []
当然执行回调时,也要改成遍历回调数组执行回调函数
最后,then方法也要改一下,只需要在最后一行加一个return this即可,这其实和jQuery链式操作的原理一致,每次调用完方法都返回自身实例,后面的方法也是实例的方法,所以可以继续执行。
编写思路
1.将所有的成功与失败回调函数存在数组中,遍历执行
2.为了支持链式调用,返回this实例对象即可
3.每次改变状态,清空所有的队列回调
4.目前这种方式只支持同步的回调,下面会加入支持异步
异步链式调用,好像是这里最难的点,但是在应用层里的难点,加一个中间层就能解决,实在不行加两个 ---来自国内不知名码农
看下面这段Node.js代码:
上面场景,我们读取完1.txt后并打印1.txt内容,再去读取2.txt并打印2.txt内容,再去读取3.txt并打印3.txt内容,而读取文件都是异步操作,所以都是返回一个promise。
我们上一节实现的promise可以实现执行完异步操作后执行后续回调,但是本节的回调读取文件内容操作并不是同步的,而是异步的,所以当读取完1.txt后,执行它回调onFulfilledCallbacks里面的f1,f2,f3时,异步操作还没有完成,所以我们本想得到这样的输出:
this is 1.txt this is 2.txt this is 3.txt
但是实际上却会输出
this is 1.txt this is 1.txt this is 1.txt
上面遇到的问题,有点像一个面试题,考闭包的一个循环。看打印输出几,大家应该有印象。
支持异步链式调用
每次.then返回一个新的promise,这个新的promise拥有它独自对应的成功和失败回调(相当于中间层)
同样每次状态改变就清空当前promise对应的回调队列
修改.then方法
.then在链式调用中会被执行多次,这里是本文的重点
思路解析:
首先判断当前Promise的状态,如果状态没有改变,那就全部添加到队列中
调用resolve函数,同样清空队列中所有的任务,不同点在于bridgePromise这个用来桥接的Promise(看成中间层),下面给出例子详解
MyPromise.prototype.then = function(onFulfilled, onRejected) {
const self = this;
let bridgePromise;
//防止使用者不传成功或失败回调函数,所以成功失败回调都给了默认回调函数
onFulfilled = typeof onFulfilled === "function" ? onFulfilled : value => value;
onRejected = typeof onRejected === "function" ? onRejected : error => { throw error };
if (self.status === FULFILLED) {
return bridgePromise = new MyPromise((resolve, reject) => {
setTimeout(() => {
try {
let x = onFulfilled(self.value);
resolvePromise(bridgePromise, x, resolve, reject);
} catch (e) {
reject(e);
}
});
})
}
if (self.status === REJECTED) {
return bridgePromise = new MyPromise((resolve, reject) => {
setTimeout(() => {
try {
let x = onRejected(self.error);
resolvePromise(bridgePromise, x, resolve, reject);
} catch (e) {
reject(e);
}
});
});
}
if (self.status === PENDING) {
return bridgePromise = new MyPromise((resolve, reject) => {
self.onFulfilledCallbacks.push((value) => {
try {
let x = onFulfilled(value);
resolvePromise(bridgePromise, x, resolve, reject);
} catch (e) {
reject(e);
}
});
self.onRejectedCallbacks.push((error) => {
try {
let x = onRejected(error);
resolvePromise(bridgePromise, x, resolve, reject);
} catch (e) {
reject(e);
}
});
});
}
}
这个例子,里面有异步的代码,但是Promise可以做到有序执行
我们看bridgePromise的源码:
代码很简单 就是递归调用,直到返回的不是一个Promise,那么调用resolve清空队列,并且把返回的值存储在self属性上,提供给下一个任务使用。
下面就是流程图:
这里一定要看清楚,本文的重点基本都在这里。
符合Promise A+规范,修改resolvePromise函数即可
function resolvePromise(bridgepromise, x, resolve, reject) {
//2.3.1规范,避免循环引用
if (bridgepromise === x) {
return reject(new TypeError('Circular reference'));
}
let called = false;
//这个判断分支其实已经可以删除,用下面那个分支代替,因为promise也是一个thenable对象
if (x instanceof MyPromise) {
if (x.status === PENDING) {
x.then(y => {
resolvePromise(bridgepromise, y, resolve, reject);
}, error => {
reject(error);
});
} else {
x.then(resolve, reject);
}
// 2.3.3规范,如果 x 为对象或者函数
} else if (x != null && ((typeof x === 'object') || (typeof x === 'function'))) {
try {
// 是否是thenable对象(具有then方法的对象/函数)
//2.3.3.1 将 then 赋为 x.then
let then = x.then;
if (typeof then === 'function') {
//2.3.3.3 如果 then 是一个函数,以x为this调用then函数,且第一个参数是resolvePromise,第二个参数是rejectPromise
then.call(x, y => {
if (called) return;
called = true;
resolvePromise(bridgepromise, y, resolve, reject);
}, error => {
if (called) return;
called = true;
reject(error);
})
} else {
//2.3.3.4 如果 then不是一个函数,则 以x为值fulfill promise。
resolve(x);
}
} catch (e) {
//2.3.3.2 如果在取x.then值时抛出了异常,则以这个异常做为原因将promise拒绝。
if (called) return;
called = true;
reject(e);
}
} else {
resolve(x);
}
}
实现es6 promise的all,race,resolve,reject方法
all方法:
race 方法
resolve,reject方法~ 快速定义一个成功或者失败状态的Promise
实现promisify方法~
实现catch方法~其实就是不传第一个参数.then方法的语法糖
到这里,一个Promise 并且符合A+规范的所有方法就实现了,网上的实现方式有很多都不一样,本文以一个比较简单明了的方式去实现了它。希望能帮助到大家更清楚的了解Promise
后面会针对下面的内容出专题系列文章~ 欢迎关注本公众号:前端巅峰
1.从零手写一个React
2.从零编写一个webpack
3.从零实现一个websocket
4.从零实现一个vue
5.优雅的让react和vue一起开发
本公众号注重关注技术方向
即时通讯
Electron跨平台桌面端、React-native移动端、Taro小程序
Node.js全栈工程师方向、分布式微服务
原生JavaScript