promise源码实现
#
前言Promise,看到这个词,大脑里第一念头想到的是微任务、解决异步回调。但是在这上面栽过跟头,记得有次面试,面试官出了一个比较简单的和promise相关的问题,可惜平日写的少不怎么会运用,面试准备都刷leetcode去了,但是面试官一道也没问。当时也有些不在状态,面试官一再让我尝试,最后也没有写出来,最后只拿到了一个白菜的offer,有些痛心疾首。promise也在网上看到很多文章写过了,这两天沉下心实现了一下,关于核心的一些点,觉得应该整理一下。
#
产生原因promise是解决异步回调地狱的一个方案,回调地狱是指对于异步操作,我们不知道异步任务何时能完成,不能一直让单线程的js等着,所以提前把回调函数注册好,当异步耗时的任务结束后,就触发回调,执行提前写好的回调函数。但是当多个异步任务之间有依赖之后,C异步任务依赖于B异步任务,B异步任务依赖于A异步任务,就会导致代码越来越胖,宽度增加,也就是说回调会一层嵌套一层,按照正常的缩进,代码会冗长且胖。社区的讨论,时代的产物,promise诞生了。
#
解决的问题&关键原理promise比较优雅的一点是:将代码从宽度变为了长度,不需要一层一层嵌套回调,只需要从上往下按着写就好。回调函数还是可以提前注册好,不过不是水平方向了,而是写到promise的then函数里,这里使用了发布订阅的设计模式,当异步任务完成后会调用resolve,进而通知预先注册好的回调函数,依次执行。
好了,源码附在最后面,下面写一下实现源码关键与巧妙的点吧。
#
Promise的三种状态一个Promise只能三种状态,pending
、fulfilled
、rejected
,分别是等待着,已完成,已拒绝。promise在没有调用resolve
方法和reject
方法之前,就是pending
状态,调用了resolve
方法之后,转换为fulfilled
状态,同理,调用了reject
方法之后,转换为rejected
状态。但是,pending只能转换为fulfilled或rejected状态的一种,且不可逆转。这是怎么实现的呢?一个标志位,status
,调用resolve
或reject
方法之后就进行标志位的赋值。
#
promise构造器的参数构造一个promise,输入的参数是一个函数x,这个函数会提供两个形参,名字随便设置,一般设为resolve
和reject
,这两个方法实际上是在Promise内部实现的。当promise构造结束后,会直接调用这个函数x。
#
promise的then方法then方法的作用是:注册回调函数。
then方法接收的参数是两个函数onResolved
和onRejected
,这两个函数就是回调函数。就是好的情况和坏的情况两种。当promise对象按照预期的逻辑正常执行时,也就是执行到调用了resolve
方法这里,就会执行onResolvd
这个回调,如果预设的逻辑有问题,或者某处出错,会执行onReject
这个回调。
then方法可以进行链式调用,因为then方法执行后返回的也是一个新的promise,同样可以执行then方法。
then方法实现的关键点。
- 输入的参数必须是函数。如果只是普通值,需要在源码内进行修饰,转换为函数。
- 一个promise对象的正确回调函数,也就是then方法内注册的第一个函数,它接受的参数是构造promise对象的函数内,执行
resolve(abc)
方法时传入的参数,也就是abc
,有可能是一个值,此时就直接执行;也有可能是一个新的promise,此时需要等待新的promise执行完毕后,看返回值是什么再拿来当做参数。这就有可能子子孙孙无穷尽,不知道下一个参数是promise还是普通值,就需要递归来处理。 - 查看当前的promise状态是什么,如果是
pending
,就把注册的成功回调和失败回调放到相应的订阅者数组中,等待resolve
或者reject
一声令下,就执行回调。
#
isPromise方法判断一个对象是不是promise,一只动物会嘎嘎叫,就认定他是鸭子。一个对象有then方法,就认为他是promise。
#
Promise.all实现此方法输入是一个数组,数组内容是promise对象或者是普通值。输出是所有promise返回的结果。如果有promise出错,就直接拒绝。
构造一个新的promise来实现,一个比较关键的点是,要根据已经完成态promise的数量来决定是否执行resolve方法。
源码如下:
const RESOLVED = 'RESOLVED';const PENDING = 'PENDING';const REJECTED = 'REJECTED';
class MyPromise { constructor(fn) { this.value = undefined; this.reason = undefined; this.status = PENDING; this.onResolvedTasks = []; this.onRejectedTasks = [];
let resolve = (val) => { if(this.status === PENDING) { // PENDING的作用,是让resolve和reject不能同时调用 this.status = RESOLVED; this.value = val; this.onResolvedTasks.forEach(cb => cb()); } }
let reject = (reason) => { if(this.status === PENDING) { this.status = REJECTED; this.reason = reason; this.onRejectedTasks.forEach(cb => cb()); } }
try { fn(resolve, reject); } catch (e) { reject(e); } }
then(onResolved, onRejected) { onRejected = typeof onRejected === 'function' ? onRejected : reason => {throw reason}; onResolved = typeof onResolved === 'function' ? onResolved : v => v; const promise2 = new MyPromise((resolve, reject) => { let x;
if(this.status === RESOLVED) { setTimeout(() => { // 这里需要使用异步,否则无法获得promise2 try { x = onResolved(this.value); this.resolvePromise(promise2, x, resolve, reject); } catch (e) { reject(e); } }, 0); }
if(this.status === REJECTED) { setTimeout(() => { try { x = onRejected(this.reason); this.resolvePromise(promise2, x, resolve, reject); } catch(e) { reject(e); } }, 0); }
if(this.status === PENDING) { this.onResolvedTasks.push( () => { setTimeout(() => { try { x = onResolved(this.value); this.resolvePromise(promise2, x, resolve, reject); } catch (e) { reject(e); } }, 0); } ); this.onRejectedTasks.push( () => { setTimeout(() => { try { x = onRejected(this.reason); this.resolvePromise(promise2, x, resolve, reject); } catch (e) { reject(e); } }) } ); } })
return promise2; }
catch(onRejected) { return this.then(undefined, onRejected); }
resolvePromise(promise, x, resolve, reject) { if(promise === x) { reject(new TypeError('Chaining cycle detected for promise!')); } if(x && typeof x === 'object' || typeof x === 'function') { let used = false; // 只调用一次 try {
let then = x.then; // 如果x是promise,则取出他的then方法, if(typeof then === 'function') { then.call(x, y => { // 对前一个then返回是promise的情况,对返回的promise注册新的方法,继续取值 if(used) return; used = true; this.resolvePromise(promise, y, resolve, reject); }, err => { if(used) return; used = true; reject(err); }); } else { if(used) return; used = true; resolve(x); // x不是一个函数,是一个对象??? } } catch (err) { if(used) return; used = true; reject(err); } } else { resolve(x); // 如果正常值的话直接resolve返回 } }
static isPromise(p) { if(p&&typeof p==='object' || typeof p === 'function') { if(typeof p.then === 'function') { return true; } } else { return false; } } static all(promises) { if(!Array.isArray(promises)) { throw new TypeError('the input should be an array!'); } return new MyPromise((resolve, reject) => { let results = []; let count = 0; let length = promises.length; function dealPromise(i, value) { results[i] = value; count += 1; if(count===length) { resolve(results); } } for(let i=0; i<length; i++) { if(this.isPromise(promises[i])) { promises[i].then(value => { dealPromise(i, value); }, reason => { reject(reason); return ; }); } else { dealPromise(i, promises[i]); } } }); }
static race(promises) { if(!Array.isArray(promises)) { throw new TypeError('the input should be an array!'); }
return new MyPromise((resolve, reject) => { let length = promises.length; for(let i=0; i<length; i++) { if(this.isPromise(promises[i])) { promises[i].then(val => { resolve(val); return ; }, err => { reject(err); return ; }) } else { resolve(promises[i]); return; } } }); }
static resolve(value) { if(this.isPromise(value)) { return value; } else { return new MyPromise((resolve, reject) => { resolve(value); }); } }
static reject(value) { return new MyPromise((resolve, reject) => { reject(value); }) }}
// 可以使用promises-aplus-tests这个npm包来检测MyPromise.defer = MyPromise.deferred = function () { let dfd = {}; dfd.promise = new MyPromise((resolve, reject) => { dfd.resolve = resolve; dfd.reject = reject; }); return dfd;}
module.exports = MyPromise
可以使用promises-aplus-tests这个npm包来检测实现的是否正确。
一开始不想写大论文,最近渐入佳境,游戏也不想打了。
written by Rain
2021.1.30 天朗气清