实现一个自己的Promise
前言
在写这个promise之前,希望你已经对es6中的Promise很熟悉了,概念性和基础的东西就不再讲了,不懂的同学可以去看看阮一峰老师的es6教程. 我主要按以下5个步骤来一步一步实现,异步的实现我放在了后面,所以前面几步暂不考虑
- 实现一个基本的MyPromise
- 实现then的链式调用
- 处理reolve函数的参数是MyPromise实例的情况以及处理then方法中前一个回调函数返回的也是一个MyPromise实例的情况
- 实现异步的MyPromise
- 完善MyPromise的其它方法
1.实现一个基本的MyPromise
/* * 这里我将promise的3个状态分别定义为: pending, resolved, rejected * 其中fn必须是个函数, 必须通过new来使用 */ function MyPromise(fn) { if (!(this instanceof MyPromise)) { throw new TypeError('MyPromise must be constructed via new'); } if (typeof fn !== 'function') { throw new TypeError('MyPromise constructor argument is not a function'); } this.state = 'pending'; // 出初始化状态 this.value = undefined; // 初始化一个值, 用来存储resolve或者reject的值 // 执行 fn 方法 executeFn(fn, this); } MyPromise.prototype.then = function(onFullfilled, onRejected) { var res = undefined; var cb = this.state === 'resolved' ? onFullfilled : onRejected; res = cb(this.value); } // 执行 fn 方法 function executeFn(fn, promise) { var done = false; // 声明一个变量, 防止resolve, reject连续调用 try { fn(function _resolve(value) { if(done) return; done = true; resolve(promise, value); }, function _reject(reason) { if(done) return; done = true; reject(promise, reason); }); } catch(err) { if(!done) { done = true; reject(promise, err); } } } function resolve(promise, value) { promise.state = 'resolved'; promise.value = value; } function reject(promise, error) { promise.state = 'rejected'; promise.value = error; }
这样就实现了一个基本版的MyPromise,调用方法同Promise,如下:
new MyPromise((resolve, reject) => { // resolve('resolved'); reject('rejected'); }).then(res => { console.log('>>> res', res); }, err => { console.log('>>> err', err); });
2.实现then的链式调用
原生Promise支持链式调用,并且then方法会返回一个新的Promise实例,第一个回调函数完成以后,会将返回结果作为参数,传入第二个回调函数,所以我们可以修改下then方法,其余不变
MyPromise.prototype.then = function(onFullfilled, onRejected) { var self = this; var res = undefined; var cb = this.state === 'resolved' ? onFullfilled : onRejected; var newPromise = new MyPromise(function(_resolve, _reject) { try { res = cb(self.value); _resolve(res); } catch(err) { _reject(err); } }); return newPromise; }
这样的话,链式调用也就实现了,测试了下,没啥问题
new MyPromise((resolve, reject) => { resolve('resolved'); // reject('rejected'); }).then(res => { console.log('>>> res', res); return 'res1'; }, err => { console.log('>>> err', err); return 'err1'; }).then(res2 => { console.log('>>> res2', res2); }, err2 => { console.log('>>> err2', err2); });
3. 处理reolve函数的参数是MyPromise实例的情况以及处理then方法中前一个回调函数返回的也是一个MyPromise实例的情况
resolve函数的参数除了正常的值以外,还可能是另一个MyPromise实例,如下,这个时候p1的状态决定了p2的状态
const p1 = new MyPromise(function(resolve, reject) { // ... }); const p2 = new MyPromise(function(resolve, reject) { // ... resolve(p1); });
同样,在then方法中,前一个回调函数有可能返回的也是一个MyPromise对象,这时后一个回调函数就会等待该MyPromise对象的状态发生变化,才会被调用,例如:
const p1 = new MyPromise(function(resolve, reject) { // ... }); const p2 = new MyPromise(function(resolve, reject) { // ... resolve('p2 resolved'); }); p2.then(res1 => { console.log('>>> res1', res1); return p1; }, err1 => { console.log('>>> err1', err1); }).then(res2 => { console.log('>>> res2', res2); }, err2 => { console.log('>>> err2', err2); })
了解以上后,我们来实现它
function resolve(promise, value) { if(!handlePromise(promise, value)) return; // 增加这行 promise.state = 'resolved'; promise.value = value; } /* * 增加一个函数用来处理返回值或者resolve的参数是promise的情况, 最后的返回值起个标识作用 */ function handlePromise(promise, value) { if(value === promise) { reject(promise, 'A promise cannot be resolved with itself'); return; } if(value && (typeof value === 'object' || typeof value === 'function')) { var then = value.then; if(typeof then === 'function') { executeFn(then.bind(value), promise); return; } } return true; } // 同时then中增加一行代码 MyPromise.prototype.then = function(onFullfilled, onRejected) { var self = this; var res = undefined; var cb = this.state === 'resolved' ? onFullfilled : onRejected; var newPromise = new MyPromise(function(_resolve, _reject) { if(self.state === 'pending') return; // 增加这行 try { res = cb(self.value); _resolve(res); } catch(err) { _reject(err); } }); return newPromise; }
到这里,就解决了以上2个问题,测试一下
const p = new MyPromise((resolve, reject) => { // resolve('resolve a promise'); reject('reject a promise'); }); // resolve参数是MyPromise实例 new MyPromise((resolve, reject) => { resolve(p); // reject('rejected'); }).then(res => { console.log('>>> res', res); return 'res1'; }, err => { console.log('>>> err', err); return 'err1'; }).then(res2 => { console.log('>>> res2', res2); }, err2 => { console.log('>>> err2', err2); }); // then第一个方法返回的是MyPromise实例 new MyPromise((resolve, reject) => { resolve('resolved'); // reject('rejected'); }).then(res => { console.log('>>> res', res); return p; }, err => { console.log('>>> err', err); return 'err1'; }).then(res2 => { console.log('>>> res2', res2); }, err2 => { console.log('>>> err2', err2); });
4. 实现异步的MyPromise
经过前面3步,MyPromise的已经实现了大半,接着我们来实现异步的MyPromise. 既然是异步,我们并不知道它什么时候结束,但是我们可以将它的异步回调存入一个数组,待它结束后执行它,好吧,其实就是观察者模式了
首先在构造函数中加上一句
function MyPromise(fn) { // ...这里省略,加上下面这行 this.callbacks = []; // 存储异步的回调方法 // 执行 fn 方法 executeFn(fn, this); }
然后在resolve和reject方法中分别加上
function resolve(promise, value) { // ...这里省略,加上下面几句 promise.callbacks.forEach(function(cb) { cb(); }); } function reject(promise, error) { // ...这里省略,加上下面几句 promise.callbacks.forEach(function(cb) { cb(); }); }
最后在then方法中,将异步的回调发存入数组中
MyPromise.prototype.then = function(onFullfilled, onRejected) { // ...这里省略,不变 var newPromise = new MyPromise(function(_resolve, _reject) { // 状态为pending时,将回调存入数组,因为then中方法也是异步执行 // 所以用setTimeout,同时直接return if(self.state === 'pending') { self.callbacks.push(function() { setTimeout(function() { // 这里需要再次判断 cb = self.state === 'resolved' ? onFullfilled : onRejected; try { res = cb(self.value); _resolve(res); } catch(err) { _reject(err); } }); }); return; } // then中是异步执行 setTimeout(function() { try { res = cb(self.value); _resolve(res); } catch(err) { _reject(err); } }); }); return newPromise; }
到这里,异步的MyPromise也就实现了,then方法代码有点乱,我们整理下
MyPromise.prototype.then = function(onFullfilled, onRejected) { // 这里删除了一部分代码 var self = this; var newPromise = new MyPromise(function(_resolve, _reject) { if(self.state === 'pending') { self.callbacks.push(function() { // 同时将这部分的代码抽成了以下方法 handleResolved(self, onFullfilled, onRejected, _resolve, _reject); }); return; } handleResolved(self, onFullfilled, onRejected, _resolve, _reject); }); return newPromise; } function handleResolved(promise, onFullfilled, onRejected, _resolve, _reject) { setTimeout(function() { var res = undefined; var cb = promise.state === 'resolved' ? onFullfilled : onRejected; // 需要对cb进行判断 if(typeof cb !== 'function') { if(promise.state === 'resolved') { _resolve(promise.value); } else { _reject(promise.value); } return; } try { res = cb(promise.value); _resolve(res); } catch(err) { _reject(err); } }); }
测试如下, 当然没啥问题了
const p = new MyPromise((resolve, reject) => { setTimeout(() => { resolve('resolve a promise'); // reject('reject a promise'); }, 2 * 1000); }); new MyPromise((resolve, reject) => { resolve(p); // reject('rejected'); }).then(res => { console.log('>>> res', res); return 'res1'; }, err => { console.log('>>> err', err); return 'err1'; }).then(res2 => { console.log('>>> res2', res2); }, err2 => { console.log('>>> err2', err2); });
到这里,一个大概的MyPromise也就基本实现完成了,整理后的完整代码如下
function MyPromise(fn) { if (!(this instanceof MyPromise)) { throw new TypeError('MyPromise must be constructed via new'); } if (typeof fn !== 'function') { throw new TypeError('MyPromise constructor argument is not a function'); } this.state = 'pending'; // 初始化状态 this.value = undefined; // 初始化一个值, 用来存储resolve或者reject的值 this.callbacks = []; // 存储异步的回调方法 // 执行 fn 方法 executeFn(fn, this); } MyPromise.prototype.then = function(onFullfilled, onRejected) { var self = this; var newPromise = new MyPromise(function(_resolve, _reject) { if(self.state === 'pending') { self.callbacks.push(function() { handleResolved(self, onFullfilled, onRejected, _resolve, _reject); }); return; } handleResolved(self, onFullfilled, onRejected, _resolve, _reject); }); return newPromise; } function handleResolved(promise, onFullfilled, onRejected, _resolve, _reject) { setTimeout(function() { var res = undefined; var cb = promise.state === 'resolved' ? onFullfilled : onRejected; if(typeof cb !== 'function') { if(promise.state === 'resolved') { _resolve(promise.value); } else { _reject(promise.value); } return; } try { res = cb(promise.value); _resolve(res); } catch(err) { _reject(err); } }); } // 执行 fn 方法 function executeFn(fn, promise) { var done = false; // 声明一个变量, 防止resolve, reject连续调用 try { fn(function _resolve(value) { if(done) return; done = true; resolve(promise, value); }, function _reject(reason) { if(done) return; done = true; reject(promise, reason); }); } catch(err) { if(!done) { done = true; reject(promise, err); } } } function resolve(promise, value) { if(!handlePromise(promise, value)) return; promise.state = 'resolved'; promise.value = value; promise.callbacks.forEach(function(cb) { cb(); }); } function reject(promise, error) { promise.state = 'rejected'; promise.value = error; promise.callbacks.forEach(function(cb) { cb(); }); } // 用来处理返回值或者resolve的参数是promise的情况, 最后的返回值起个标识作用 function handlePromise(promise, value) { if(value === promise) { reject(promise, 'A promise cannot be resolved with itself'); return; } if(value && (typeof value === 'object' || typeof value === 'function')) { var then = value.then; if(typeof then === 'function') { executeFn(then.bind(value), promise); return; } } return true; }
5. 最后来实现下MyPromise的其它方法
MyPromise.prototype.catch = function(onRejected) { return this.then(null, onRejected); } // 只要不是pending状态都会执行 MyPromise.prototype.finally = function(cb) { return this.then( function(value) { return MyPromise.resolve(cb()).then(function() { return value; }); }, function(err) { return MyPromise.resolve(cb()).then(function() { throw err; }); } ); } MyPromise.resolve = function(val) { return new MyPromise(function(resolve, reject) { resolve(val); }); } MyPromise.reject = function(err) { return new MyPromise(function(resolve, reject) { reject(err); }); } /* * all方法用于将多个 MyPromise 实例,包装成一个新的 MyPromise 实例 * 只有全部实例都resolved,才会resolve; 只要其中一个rejected,就会reject * 参数可以不是数组,但必须具有 Iterator 接口, 同时里面的值可能也不是promise实例 */ MyPromise.all = function(promiseArr) { var args = [].slice.call(promiseArr); return new MyPromise(function(resolve, reject) { var arr = []; var resolveCount = 0; var argsLen = args.length; for(var i = 0; i < argsLen; i++) { handle(i, args[i]); } function handle(index, val) { MyPromise.resolve(val).then(function(value) { arr[index] = value; if(++resolveCount === argsLen) { resolve(arr); } }, reject); } }); } /* * race方法与all方法类似,只要其中一个实例状态发生改变resolved / rejected即可 * 参数可以不是数组,但必须具有 Iterator 接口, 同时里面的值可能也不是promise实例 */ MyPromise.race = function(promiseArr) { var args = [].slice.call(promiseArr); return new MyPromise(function(resolve, reject) { for(var i = 0; i < args.length; i++) { MyPromise.resolve(args[i]).then(resolve, reject); } }); }
至此Promise的实现就算完成了,完整代码的地址点这里
最后
说点题外话,在面试的过程中,经常会遇见面试官要求现场实现一个Promise,看了这篇文章后希望对你有所帮助,已经能够实现一个Promise了,而对于面试中其他的promise的相关问题能够轻松应对