说到异步编程,大家应该会想到Promise。想必对它对典型的认知应该就是——解决了回调地狱的问题,通过链式调用(不停的.then .then)的方式来避免大量嵌套。对于我个人而言,在使用Promise的过程中还是难免出现一些问题,感觉仍停留在稀里糊涂用的这么一个阶段。今天我想通过实现一个Promise来彻底搞懂其原理。
Promise我们可以称之为一个容器,它里边保存了一个异步操作的最终结果。这里有一个Promises/A+规范,我们可以按照其中的描述来一步一步的实现一个符合要求的Promise。
Promise有三个状态,等待、成功和失败,默认是等待状态。并且一旦成功或者失败了状态就不能改变了。
resolve接收一个函数作为参数,我们称之为excutor,它是一个执行器,会立即执行。它有两个参数resolve和reject,分别代表成功和失败。resolve代表的是成功,它会接收一个参数value,状态改变为fulfilled。reject代表的是失败,接收参数reason,状态改变为rejected。这里要注意的是,状态变为成功或者失败后不能再次被更改,如果new Promise的时候throw new Error报错也会变成失败态。
每个Promise应该有自己的三个状态,因此我们将它放在构造函数里。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| const PENDING = 'PENDING'; const RESOLVED = 'RESOLVED'; const REJECTED = 'REJECTED';
class Promise{ constructor(executor){ this.status = PENDING; this.value = undefined; this.error = undefined; let resolve = value => { if (this.status === PENDING) this.status = RESOLVED; this.value = value; } }; let reject = error => { if (this.status === PENDING) { this.status = REJECTED; this.error = error; } }; try{ executor(resolve, reject); } catch (e) { reject(e); } } }
|
每个promise实例都有一个then方法,可以传入两个参数,onFulfilled和onRejected。如果当前状态成功我们调用onfFlfilled,失败调用onRejected.
1 2 3 4 5 6 7 8 9 10
| then(onFulfilled,onRejected) { if (this.status === RESOLVED) { onFulfilled(this.value); }; if (this.state === REJECTED) { onRejected(this.error); }; }
|
当resolve在setTomeout里运行的时候,上边的写法就不能满足要求啦。我们可以采取发布订阅的思想,现将要执行的方法存到数组里,当状态改变后再执行对应的方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| let resolve = value => { if (this.state === PENDING) { this.state = RESOLVED; this.value = value; this.onResolvedCallbacks.forEach(fn=>fn()); } }; let reject = error => { if (this.status === PENDING) { this.status = REJECTED; this.error = reason; this.onRejectedCallbacks.forEach(fn=>fn()); } };
then(onFulfilled, onRejected) { if (this.status === PENDING) { this.onResolvedCallbacks.push(()=>{ onFulfilled(this.value); }) this.onRejectedCallbacks.push(()=>{ onRejected(this.error); }) } }
|
我们需要判断then中传递函数的返回结果。当返回结果是promise时,则采用其状态。如果不是promise。将结果传递下去即可。Promise通过链式调用来解决回调地狱,也就是在第一个then里返回里一个Promise。我们可以在then里面返回一个新的promise,称为promise2。
1 2 3 4 5 6
| let promise2 = new Promise((resolve, reject)=>{ if (this.status === RESOLVED) { let x = onFulfilled(this.value); resolvePromise(promise2, x, resolve, reject); };
|
接下来我们来实现resolvePromise这个方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| function resolvePromise(promise2, x, resolve, reject){ if(x === promise2){ return reject(new TypeError('循环引用啦')); } let called; if (x != null && (typeof x === 'object' || typeof x === 'function')) { try { let then = x.then; if (typeof then === 'function') { then.call(x, y => { if (called) return; called = true; resolvePromise(promise2, y, resolve, reject); }, err => { if (called) return; called = true; reject(err); }) } else { resolve(x); } } catch (e) { if (called) return; called = true; reject(e); } } else { resolve(x); } }
|
onFulfilled和onRejected均为可选参数。
onFulfilled返回一个普通的值,成功时直接等于 data => data
onRejected返回一个普通的值,失败时如果直接等于 data => data,则会跑到下一个then中的onFulfilled中,所以直接扔出一个错误
1 2 3
| onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : data => data;
onRejected = typeof onRejected === 'function' ? onRejected : err => { throw err };
|
实现了Promise,那么它的all方法和race方法相对来说就比较好实现了。
Promise.all()方法用于将多个 Promise 实例,包装成一个新的 Promise 实例。
只有传入所有实例的状态都变成fulfilled,新的promises的状态才会变成fulfilled,此时每个实例的返回值组成一个数组,传递给promises的回调函数。
只要之中有一个被rejected,p的状态就变成rejected,此时第一个被reject的实例的返回值,会传递给整个promises的回调函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| Promise.all = function (promises) { return new Promise(function (resolve, reject) { let arr = []; let successCount = 0; function processData(index, data) { arr[index] = data; successCount++; if (successCount === promises.length) { resolve(arr); } } for (let i = 0; i < promises.length; i++) { promises[i].then(function (data) { processData(i, data) }, reject) } }) }
|
Promise.race也是将多个Promise实例包装成一个新的实例,只要有其中一个实例改变状态那么整个实例状态改变并停止执行。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| Promise.race = function(promises) { return new Promise((resolve, reject) => { let len = promises.length; if(len === 0) return; for(let i = 0; i < len; i++) { Promise.resolve(promise[i]).then(data => { resolve(data); return; }).catch(err => { reject(err); return; }) } }) }
|