es5如何实现promise_Promise实现原理解析

本文深入探讨Promise的实现原理,包括状态机、转换器、构造器和观察者模式。通过基本实现和扩展功能,如catch、finally、Promise.resolve、Promise.reject、Promise.all和Promise.race的解析,帮助读者理解Promise的工作机制和使用。
摘要由CSDN通过智能技术生成

前言

大家平时基本上都听过或者用过Promise,大部分基本都是拿来主义(当然也包括我),而对于原理知之甚少,这样不利于日后问题的排查和代码的优化。为此,我写这篇文章来加深对Promise的理解和使用。本文假设读者都是对Promise的使用有一定了解的基础上展开的,因此没对Promise的基本使用做过多的介绍。然后,我将通过以下几个部分来讲解:基本实现

扩展功能实现

扩展API实现

基本实现

State Machine(状态机)

Promise本质上其实是一个状态机,由PENDING、FULFILLED、REJECTED 这个三个状态组成,初始状态为PENDING。因此,我们创建一个简单的状态机,state为当前的状态,value为结果值,deferrals为延迟处理的任务(当Promise状态处于PENDING时添加的任务),deferralsState为延迟任务的数量(0表示没有延迟任务,1表示一个延迟任务,2表示两个及以上延迟任务)。

var PENDING = 0;

var FULFILLED = 1;

var REJECTED = 2;

function Promise() {

this.state = PENDING;

this.value = null;

this.deferralsState = 0;

this.deferrals = null;

}

Transitions(转换器)

根据规定,Promise状态的改变是有限制的,即只能PENDING->FULFILLED或PENDING->REJECTED,而且这种改变是不可逆的。除此之外,当处于FULFILLED状态下,Promise必须要一个value,而且这个value是不可改变的;当处于REJECTED状态下,Promise必须要有一个reason(报错),而且这个reason也是不可改变的。

我们创建resolve和reject来表示Promise状态的改变并把调用者传递过来的值赋给value。可是调用者可能传各种类型的值过来,如果传Promise进来,我们是无法直接取值的,那么我们就通过then函数(后面会介绍)来取值,并通过递归调用doResolve函数(后面会介绍)来把参数Promise中的value传递到我们当前Promise中,而递归调用的目的是为了防止多重嵌套情况的发生。那finale的作用是什么呢?之前我们说过,当Promise还处于PENDING状态的时候,我们会把添加的待办任务放入deferrals中,在状态改为FULFILLED或 REJECTED后,我们统一处理。

function resolve(self, newValue) {

if (

newValue &&

(typeof newValue === "object" || typeof newValue === "function")

) {

var then = getThen(newValue);

if (then === IS_ERROR) {

return reject(self, LAST_ERROR);

}

if (typeof then === "function") {

doResolve(then.bind(newValue), self);

return;

}

}

self.state = FULFILLED;

self.value = newValue;

finale(self);

}

function reject(self, newValue) {

self.state = REJECTED;

self.value = newValue;

finale(self);

}

function finale(self) {

if (self.deferralsState === 1) {

handle(self, self.deferrals);

this.deferrals = null;

}

if (self.deferralsState === 2) {

for (var i = 0; i < self.deferrals.length; i++) {

handle(self, self.deferrals[i]);

}

self.deferrals = null;

}

}

Constructing(构造器)

讲完了Promise状态的改变,我们再讲讲如何触发状态的改变。根据我们使用Promise的经验,它会提供resolve和reject两个回调函数让我们调用,正常情况下调用resolve并传参,异常情况下则调用reject并传入 异常信息。所以,我们在Promise的构造函数中运行doResolve,将回调函数传给调用者。当然,doResolve的作用不仅仅是这样,我们之前讲过,Promise的状态改变是单向的,而且是不可逆的,为了达到这个目的,我们在doResolve中做了控制,保证当前Promise中的resolve 或reject只执行一次。

function Promise(fn) {

this.state = PENDING;

this.value = null;

this.deferralsState = 0;

this.deferrals = null;

doResolve(fn, this);

}

function doResolve(fn, promise) {

var done = false;

var res = tryCallTwo(

fn,

function (value) {

if (done) {

return;

}

done = true;

resolve(promise, value);

},

function (reason) {

if (done) {

return;

}

done = true;

reject(promise, reason);

}

);

if (!done && res === IS_ERROR) {

done = true;

reject(promise, LAST_ERROR);

}

}

Observing(观察者模式)

在状态改变完之后,Promise需要做什么呢?肯定是要通知调用者(将异步操作的结果返回给调用者),而这一切是通过then函数来实现的。在实际使用中,我们从then函数提供的两个回调函数onFulfilled或onRejected中获取Promise中的value, 因此我们创建函数handleResolved ,根据当前Promise状态来选择调用onFulfilled或onRejected,可是,如果当前的Promise状态为PENDING时,逻辑上,无论我们调用哪一个都是不合适的,既然这样,那我们通过构造函数Handler把要执行的任务封装起来并存入deferrals中 ,等到执行resolve时,将存储在deferrals 中的任务给依次执行完。此外,Promise中的then是支持链式调用的,所以,我们在then中返回一个新的Promise,并且在Promise的构造函数中加了对doResolve函数执行的拦截,因为这个时候,我们并需要改变Promise的状态。 接下来,我们再考虑一种情况,即调用者不使用then函数提供的回调方法。这样新返回的Promise中的value是null ,为了解决这个问题,我们把新创建的Promise也在Handler中封装起来, 如果调用者没有调用then提供的回到函数,我们就自己调用resolve,将value值传递给新的Promise中,以备调用者使用。 另外,就算用户调用了onFulfilled或 onRejected,我们也把它们的返回值保存下来(调用resolve),达到数据往下传递的目的。

var asap = require("asap/raw");

function noop() {}

function Promise(fn) {

this.state = PENDING;

this.value = null;

this.deferralsState = 0;

this.deferrals = null;

if (fn === noop) {

return;

}

doResolve(fn, this);

}

Promise.prototype.then = function (onFulfilled, onRejected) {

var res = new Promise(noop);

handle(this, new Handler(onFulfilled, onRejected, res));

return res;

};

function Handler(onFulfilled, onRejected, promise) {

this.onFulfilled = typeof onFulfilled === "function" ? onFulfilled : null;

this.onRejected = typeof onRejected === "function" ? onRejected : null;

this.promise = promise;

}

function handle(self, deferral) {

if (self.state === 0) {

if (self.deferralsState === 0) {

self.deferralsState = 1;

self.deferral = deferral;

return;

}

if (self.deferralsState === 1) {

self.deferralsState = 2;

self.deferrals = [self.deferrals, deferral];

return;

}

self.deferrals.push(deferral);

return;

}

handleResolved(self, deferral);

}

function handleResolved(self, deferral) {

asap(function () {

var cb = self.state === 1 ? deferral.onFulfilled : deferral.onRejected;

if (cd === null) {

if (self.state === 1) {

resolve(deferral.promise, self.value);

} else {

reject(deferral.promise, self.value);

}

return;

}

var ret = tryCallOne(cb, self.value);

if (ret === IS_ERROR) {

reject(deferral.promise, LAST_ERROR);

} else {

resolve(deferral.promise, ret);

}

});

}

扩展功能实现

catch

除了通过在then函数中调用onRejected回调函数获取错误信息之外,我们也可以通过调用catch函数来实现相同的功能,这样在表达上会更加的清晰。而catch的实现也非常的简单,通过主动调用then的方式就可以获取当前的value的值。

Promise.prototype['catch'] = function (onRejected) {

return this.then(null, onRejected);

};

finally

finally()方法用于指定不管 Promise 对象最后状态如何,都会执行的操作。为此,我们可以调用then方法来得知当前的Promise是处于哪种状态,通过我们在onFulfilled和onRejected回调函数中都执行传入finally中的回调函数来达到无论何种情况都会执行 finally的操作。返回Promise.resolve除了能保证Promise链式调用的特性还起到构造Promise实例的作用,这个后面会讲到。

Promise.prototype.finally = function (f) {

return this.then(function (value) {

return Promise.resolve(f()).then(function () {

return value;

});

}, function (err) {

return Promise.resolve(f()).then(function () {

throw err;

});

});

};

扩展API实现

Promise.resolve

有时需要将现有对象转为 Promise 对象,Promise.resolve()方法就起到这个作用。需要注意的是,我们会针对value的类型来做不同的处理:当value是Promise实例时,我们原封不动的将它返回。

当value是thenable对象(具有then方法的对象) 时,我们将这个对象转为 Promise 对象,然后就立即执行thenable对象的then方法。

如果参数是一个原始值,或者是一个不具有then方法的对象,则Promise.resolve方法返回一个新的 Promise 对象,状态为FULFILLED。

var TRUE = valuePromise(true);

var FALSE = valuePromise(false);

var NULL = valuePromise(null);

var UNDEFINED = valuePromise(undefined);

var ZERO = valuePromise(0);

var EMPTYSTRING = valuePromise('');

function valuePromise(value) {

var p = new Promise(Promise._noop);

p.state = 1;

p.value = value;

return p;

}

Promise.resolve = function (value) {

if (value instanceof Promise) return value;

if (value === null) return NULL;

if (value === undefined) return UNDEFINED;

if (value === true) return TRUE;

if (value === false) return FALSE;

if (value === 0) return ZERO;

if (value === '') return EMPTYSTRING;

if (typeof value === 'object' || typeof value === 'function') {

try {

var then = value.then;

if (typeof then === 'function') {

return new Promise(then.bind(value));

}

} catch (ex) {

return new Promise(function (resolve, reject) {

reject(ex);

});

}

}

return valuePromise(value);

};

Promise.reject

Promise.reject返回一个 REJECTED状态的Promise。这个在代码上非常简单,我们直接放回一个新的Promise并调用reject函数就可以了。

Promise.reject = function (value) {

return new Promise(function (resolve, reject) {

reject(value);

});

};

Promise.all

Promise.all()方法用于将多个 Promise 实例,包装成一个新的 Promise 实例。而且,传入的Promise实例的状态都变为FULFILLED或者有一个 Promise实例变为REJECTED时,Promise状态发生改变。代码上,我们获取传入的Promise实例,然后依次转入函数ret中执行。ret函数会对传入 参数做以下处理:如果val是基本值类型则直接将args中相应的Promise实例替换掉来表示计算的结果。

如果val是Promise类型且状态为FULFILLED,则取出该实例中的值并递归调用ret函数。

如果val是Promise类型且状态为REJECTED,直接调用reject ,Promise.all()处理完成(Promise的特性,前面说过)。

如果val是 thenable对象,则将他转换为Promise对象,然后执行上面的操作。

最后,Promise实例都执行完成且状态都为FULFILLED,则调用resolve将结果传递出去。

var iterableToArray = function (iterable) {

if (typeof Array.from === 'function') {

// ES2015+, iterables exist iterableToArray = Array.from;

return Array.from(iterable);

}

// ES5, only arrays and array-likes exist iterableToArray = function (x) { return Array.prototype.slice.call(x); };

return Array.prototype.slice.call(iterable);

}

Promise.all = function (arr) {

var args = iterableToArray(arr);

return new Promise(function (resolve, reject) {

if (args.length === 0) return resolve([]);

var remaining = args.length;

function res(i, val) {

if (val && (typeof val === 'object' || typeof val === 'function')) {

if (val instanceof Promise && val.then === Promise.prototype.then) {

while (val._state === 3) {

val = val._value;

}

if (val.state === 1) return res(i, val.value);

if (val.state === 2) reject(val.value);

val.then(function (val) {

res(i, val);

}, reject);

return;

} else {

var then = val.then;

if (typeof then === 'function') {

var p = new Promise(then.bind(val));

p.then(function (val) {

res(i, val);

}, reject);

return;

}

}

}

args[i] = val;

if (--remaining === 0) {

resolve(args);

}

}

for (var i = 0; i < args.length; i++) {

res(i, args[i]);

}

});

};

Promise.race

Promise.race()方法同样是将多个 Promise 实例,包装成一个新的 Promise 实例。但和Promise.all() 不同的是,Promise.race()中的实例是相互竞争的关系,谁先执行,本次的结果就是谁。代码上,相对会简单很多,我们直接遍历执行resolve和reject就可以了。

Promise.race = function (values) {

return new Promise(function (resolve, reject) {

iterableToArray(values).forEach(function(value){

Promise.resolve(value).then(resolve, reject);

});

});

};

总结

本文从Promise基本的几个组成部分:State Machine(状态机)、Transitions(转换器)、 Constructing(构造器)、Observing(观察者模式),来解析Promise基本的实现原理,之后进一步解析了Promise扩展功能以及API的实现,最后希望这篇文章对大家能有些许的帮助。

参考文档

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值