一篇文章用ES6手撸一个Promise
1. Promise的原理
原理:promise对象有三种状态,pending、fulfilled和rejected。promise对象内部保存一个需要执行一段时间的异步操作,当异步操作执行结束后可以调用resolve或reject方法,来改变promise对象的状态,状态一旦改变就不能再变。new一个promise后可以通过then方法,指定resolved和rejected时的回调函数。下面是我们日常使用Promise的代码逻辑。let getAsyncData = new Promise((resolve, reject) => { // 执行一些异步操作 if (// 如果成功) { // ...执行代码 resolve(); } else { // 如果失败 // ...执行代码 reject(); } }) ); getAsyncData.then(success, fail).then(success, fail)
结合Promise A+规范,我们就可以分析一下我们要实现一个什么东西:
- 实现一个状态机,有三个状态,pending、fulfilled、rejected,状态之间的转化只能是pending->fulfilled、pending->rejected,状态变化不可逆。
- 实现一个then方法,可以用来设置成功和失败的回调
- then方法要能被调用多次,所以then方法需要每次返回一个新的promise对象,这样才能支持链式调用。
- 构造函数内部要有一个value值,用来保存上次执行的结果值,如果报错,则保存的是异常信息。
那我们现在就按照上面提到的原理和规范来实现这个Promise构造函数。
2. 实现
2.1 实现状态机
// promise.js class Promise { constructor (executor) { this.status = PENDING; this.value = ''; executor(this.resolve, this.reject); } resolve (value) { if (this.status === PENDING) { this.value = value; this.status = FULFILLED; } } reject (value) { if (this.status === PENDING) { this.value = value; this.status = REJECTED; } } then (onfulfilled, onrejected) { if (this.status === FULFILLED) { onfulfilled(this.value); } if (this.status === REJECTED) { onrejected(this.value); } } } const PENDING = 'pending'; const FULFILLED = 'fulfilled'; const REJECTED = 'rejected'; const test = new Promise((resolve, reject) => { resolve(100); }); test.then((data) => { console.log(data); },(data) => {});
因为Promise是一个构造函数,使用ES6的写法,首先想到的就是有显式constructor声明的class。上面就是我们用class的实现,可以看到这样我们就实现了这个状态机,有status, value两个属性和resolve, reject, then三个函数;同时它有pending, fulfilled和rejected三个状态,其中pending就可以切换为fulfilled或者rejected两种。
看来起还行的样子,尝试着运行一下,报错了。
ReferenceError: resolve is not defined
这是因为在class中使用this要格外小心,类的方法内部如果含有this,它默认指向类的实例,而如果单独使用这个方法(上面代码中的resolve(100)),this就会指向该方法运行时所在的环境,从而因为找不到这个方法而报错。所以,要在构造函数中绑定this。constructor改为
constructor (executor) { this.status = PENDING; this.value = ''; this.resolve = this.resolve.bind(this); this.reject = this.reject.bind(this); this.then = this.then.bind(this); executor(this.resolve, this.reject); }
再运行一下,输出了100
,但是现在其实不是一个异步处理方案,代码先运行了resolve(100)然后又运行了then函数,其实对于异步的情况没有处理,不信的话就给resolve加一个setTimeout,好了,代码又没有输出了。
2.2 实现异步设置状态
作为一个异步处理的函数,在使用的时候,我们肯定是会先设置好不同异步返回后的处理逻辑(即then的成功、失败调用函数),然后安心等待异步执行,最后再异步结束后,系统会自动根据我们的逻辑选择调用不同回调函数。换句话说,then函数要对status为pending的状态进行处理。处理的原理是设置两个数组,在pending状态下分别保存成功和失败回调函数,当状态改变后,再根据状态去调用数组中保存的回调函数。
我们将代码改变如下:
class Promise { constructor (executor) { this.status = PENDING; this.value = ''; this.onfulfilledArr = []; this.onrejectedArr = []; this.resolve = this.resolve.bind(this); this.reject = this.reject.bind(this); this.then = this.then.bind(this); executor(this.resolve, this.reject); } resolve (value) { if (this.status === PENDING) { this.value = value; this.onfulfilledArr.forEach(item => { item(this.value); }) this.status = FULFILLED; } } reject (value) { if (this.status === PENDING) { this.value = value; this.onrejectedArr.forEach(item => { item(this.value); }) this.status = REJECTED; } } then (onfulfilled, onrejected) { if (this.status === FULFILLED) { onfulfilled(this.value); } if (this.status === REJECTED) { onrejected(this.value); } if (this.status === PENDING) { this.onfulfilledArr.push(onfulfilled); this.onrejectedArr.push(onrejected); } } } const PENDING = 'pending'; const FULFILLED = 'fulfilled'; const REJECTED = 'rejected'; const test = new Promise((resolve, reject) => { setTimeout(() => { resolve(100); }, 2000) }); test.then((data) => { console.log(data); },(data) => {});
再运行一下,ok正常输出了。但是Promise的一大特点就是可以链式调用,即test.then(success, fail).then(success, fail)...这就需要then返回一个新的Promise对象,而我们的程序现在明显的是不支持的。那么继续改一下。
2.3 实现链式调用
再观察一下链式调用,如果成功和失败的函数中有返回值,这个值要作为参数传给下个then函数的成功或失败回调。所以我们要在返回的new Promise中调用相应的函数。
class Promise { constructor (executor) { this.status = PENDING; this.value = ''; this.onfulfilledArr = []; this.onrejectedArr = []; this.resolve = this.resolve.bind(this); this.reject = this.reject.bind(this); this.then = this.then.bind(this); executor(this.resolve, this.reject); } resolve (value) { if (this.status === PENDING) { this.value = value; this.onfulfilledArr.forEach(item => { item(this.value); }) this.status = FULFILLED; } } reject (value) { if (this.status === PENDING) { this.value = value; this.onrejectedArr.forEach(item => { item(this.value); }) this.status = REJECTED; } } then (onfulfilled, onrejected) { if (this.status === FULFILLED) { const res = onfulfilled(this.value); return new Promise(function(resolve, reject) { resolve(res); }) } if (this.status === REJECTED) { const res = onrejected(this.value); return new Promise(function(resolve, reject) { reject(res); }) } if (this.status === PENDING) { const self = this; return new Promise(function(resolve, reject) { self.onfulfilledArr.push(() => { const res = onfulfilled(self.value) resolve(res); }); self.onrejectedArr.push(() => { const res = onrejected(self.value) reject(res); }); }) } } } const PENDING = 'pending'; const FULFILLED = 'fulfilled'; const REJECTED = 'rejected'; const test = new Promise((resolve, reject) => { setTimeout(() => { resolve(100); }, 2000) }); test.then((data) => { console.log(data); return data + 5; },(data) => {}) .then((data) => { console.log(data) },(data) => {});
再运行一下,输出100,105。好了,一个简单的Promise就实现好了。
3. 总结
Promise其实就是对异步操作的一种封装方式,可以使得回调的流程变得清晰一些,但是本质上并不解决回调地狱。因为如果有多个异步操作嵌套,then也要一直写下去。所以后来ES6又有了Generator,允许我们用同步的方式来写异步的代码,以及它的语法糖async/await,当然这就是后话了。