Promise 完全解析
概念
Promise 主要用于解决异步编程场景的痛点。在 Promise 之前,异步编程主要靠回调函数来实现。
Promise 拥有 3 种状态:
- pending
- fulfilled
- rejected
初始状态为 pending,当发生状态转移后,则状态不会再被修改。
then
catch
finally
方法都会获取上下文的执行结果,根据成功或失败进入不同的回调方法,并返回一个新生成的 promise,这个 promise 的结果根据回调方法的执行成功与否决定。
Promise 还有几个静态方法:
Promise.all:相当于内部有一个计数器,当所有 promises 执行成功后返回所有成功结果的集合,如果有一个失败,则立即进入失败的逻辑并传递这个失败信息给回调。
Promise.resolve/Promise.reject
Promise.reject 直接返回 rejected 状态的 Promise 实例。
但对于 Promise.resolve 有以下几个情况:
无参数:返回 resolved 状态的 Promise 对象
普通数据对象: 返回 resolved 状态的 Promise 对象
一个 Promise 实例:直接返回当前实例
一个 thenable 对象(thenable 对象指的是具有 then 方法的对象) :转为 Promise 对象,并立即执行 thenable 对象的 then 方法。
// 1.Promise.resolve().then(function (value) {console.log(value) // undefined},function (value) {// 不会被调用},)// 2.Promise.resolve('Success').then(function (value) {console.log(value) // "Success"},function (value) {// 不会被调用},)// 3.var original = Promise.resolve(33)var cast = Promise.resolve(original)cast.then(function (value) {console.log('value: ' + value)})console.log('original === cast ? ' + (original === cast))/** 打印顺序如下,这里有一个同步异步先后执行的区别* original === cast ? true* value: 33*/// 4.var p1 = Promise.resolve({then: function (onFulfill, onReject) {onFulfill('fulfilled!')},})console.log(p1 instanceof Promise) // true,这是一个 Promise 对象p1.then(function (v) {console.log(v) // 输出"fulfilled!"},function (e) {// 不会被调用},)
- Promise.race 内部创建了一个新的 Promise,在构造函数中把传入的所有 promises 循环调用,并在 then 方法中调用新 Promise 的 resolve 和 rejected, 当获得第一个 promise 执行结果时则等于新的 Promise 获得了状态变更。
核心实现
Constructor 实现
- 初始化 Promise 内部的不可二次修改的内部状态 state,且仅有三种状态
pending
,fulfilled
,rejected
- 初始化内部初始值 value,初始为 undefined,可以是
undefined
,thenable
,promise
- 实现 Promise 构造函数接受参数的功能。我们会向构造函数传入一个接受
resolve, reject
作为参数的方法resolver
。
class Promise {constructor(resolver) {if (typeof resolver !== 'function') {throw new TypeError('resolver must be a function')}this._state = internal.PENDINGthis._value = undefined// 代表不是由 Promise 内部创建,由用户调用if (resolver !== internal.noop) {safelyResolveThenable(this, resolver)}}}
safelyResolveThenable
safelyResolveThenable
通过 try...catch
执行了参数构造方法 resolver,并根据成功执行与否,执行对应的 onSuccess
或 onError
方法,分别调用内部 resolve 和 reject 处理构造函数接受的 resolver 方法。
此处注意方法内部声明了 called
变量。该变量为了实现 Promise 仅可以修改一次内部 state
状态。使用 called 标记后,在构造函数多次调用 resolve/reject 都会被拦截。
function safelyResolveThenable(self, resolver) {let called = false // 标记该promise是否被调用过function onError(value) {if (called) {return}called = truereject(self, value)}function onSuccess(value) {if (called) {return}called = trueresolve(self, value)}function tryToUnwrap() {resolver(onSuccess, onError)}const result = tryCatch(tryToUnwrap)if (result.status === 'error') {onError(result.value)}}
内部 resolve, reject 实现
- 如果 resolver 传入 resolve 的是 Promise,则通过 safelyResolveThenable 继续执行该 promise。
- 当是一个非 Promise 的值时,修改成对应的状态,并为 value 赋予该值。
// ver 1.0const resolve = function (self, value) {const result = tryCatch(getThen, value) // 通过 try..catch 执行 then(onFulfilled(value), onRejected(value))if (result.status === 'error') {return reject(self, result.value)}const thenable = result.valueif (thenable) {safelyResolveThenable(self, value)} else {self._state = FULFILLEDself._value = value}return self}const reject = function (self, error) {self._state = REJECTEDself._value = errorreturn self}
以上,通过构造函数声明了一个 Promise 实例,并返回。同时,Promise 实例可以通过 then
方法来调用成功或失败的回调方法。
then
如下是判断一个合法 Promise 的方式
const isPromise = promise => {return (promise &&(typeof promise === 'object' || typeof promise === 'function') &&typeof promise.then === 'function')}
通过判断 then 是否是方法来判断是不是一个 promise,由此可见 then
是 Promise 中的一个重要实现。
then
接受 2 个方法参数,分别在成功、失败时调用。
此时需要引入 待执行队列
这个概念。在声明 Promise 时,如果内部状态为 pending
,那么所有的 then
注册方法,都会添加到待执行队列。随后状态变更后,在 resolve
和 reject
方法中需要增加调用队列方法。
function then(onFulfilled, onRejected) {// 构造一个新的 promise 用于 then 返回新的 Promise 实例const _promise = new this.constructor(internal.noop)if (this._state !== PENDING) {// 根据非 pending 状态决定回调策略const handler = this._state === FULFILLED ? onFulfilled : onRejected// unwrap 是异步执行方法,稍后介绍internal.unwrap(_promise, handler, this._value)} else {// 如果是 pending 状态,将 then 携带的方法添加到待执行队列中this._subscribers.push(new SubscriberItem(_promise, onFulfilled, onRejected))}return _promise}
SubscriberItem
添加待执行任务项时,会判断是否有处理成功/失败的方法。
假设当前 then 序号为 p1,如果 p1 提供了处理函数,则 p1 的后续待执行任务的 promise 上下文实例会变成 p1 创建的 promise。
假设 p1 没有提供处理 onRejected 的函数,则后续的待执行任务的 promise 上下文还是 p1 执行时的 promise 上下文。
class SubscriberItem {constructor(promise, onFulfilled, onRejected) {this.promise = promiseif (utils.isFunction(onFulfilled)) {this.onFulfilled = onFulfilledthis.callFulfilled = this.otherCallFulfilled}if (utils.isFunction(onRejected)) {this.onRejected = onRejectedthis.callRejected = this.otherCallRejected}}callFulfilled(value) {internal.resolve(this.promise, value)}otherCallFulfilled(value) {internal.unwrap(this.promise, this.onFulfilled, value)}callRejected(value) {internal.reject(this.promise, value)}otherCallRejected(value) {internal.unwrap(this.promise, this.onRejected, value)}}
通过 then 方法向 Promise 实例中添加了待执行队列的子项目后,当构造函数内的异步方法执行完后,需要根据状态执行待执行队列中任务对应的 onFulfilled/onRejected 回调。
// 在 resolve/reject 中添加调用 待执行队列 中方法的实现const resolve = function (self, value) {// ...ver 1.0// 如果 then 成功且有返回值const thenable = result.valueif (thenable) {safelyResolveThenable(self, thenable)} else {let i = 0const len = self._subscribers.lengthwhile (i < len) {self._subscribers[i].callFulfilled(value)i++}}// ...}const reject = function (self, error) {// ...let i = 0const length_ = self._subscribers.lengthwhile (i < length_) {self._subscribers[i].callRejected(error)i++}return self}
实现异步执行 then 方法
在时间循环中,我们知道 JavaScript 的执行 按照 macro script - micro script - macro script
的顺序循环执行,对于 Promise 的构造方法来说是在 macro script 中同步执行的,对于 then 携带的回调方法则是在下一个 micro script 的循环中执行。
在我们的实现中,resolve 中的 callFulfilled 和 reject 中的 callRejected 都会异步执行,执行时都是通过 unwrap 执行对应的 onFulfilled/onRejected 回调。
并且为回调返回值再次调用 then 中新生成的 promise 对应的 resolve/reject,从而形成 then 链。
/*** promise: promise 上下文实例* func: 根据状态传入 onFulfilled / onRejected 方法* value: 给func方法的值参数**/function unwrap(promise, func, value) {// 需要环境支持 immediate 或 使用 setTimeout 降级immediate(function () {let returnValuetry {returnValue = func(value) // 对当前 then 中的值调用 resolver 方法获得 returnValue} catch (error) {return reject(promise, error)}// 如果返回值 与 promise 一样,则说明 promise 返回了自身,不允许返回自身所以报错if (returnValue === promise) {reject(promise, new TypeError('Cannot resolve promise with itself'))} else {resolve(promise, returnValue)}})}
其他方法
.catch(), .finally()
promise.catch() 相当于只传入了 onRejected 方法,在 rejected 状态下才会执行 catch 回调。reject 的错误会一直沿 then 链向后传递,直到被 onRejected 捕获。
promise.finally() 相当于 onFulfilled/onRejected 传入了同一个方法,这样等于成功、失败都会执行。
class Promise {// ...catch(onRejected) {return this.then(null, onRejected)}finally(callback) {if (typeof callback !== 'function') {return this}const constructor = this.constructorreturn this.then(function (value) {// 这段代码是为了满足标准设计,对于 finally 返回的 promise , Promise.resolve(promise) 仍然会把finally 上一次的 then 结果带过来return constructor.resolve(callback()).then(function () {return value})},function (reason) {// 同理 抛出错误return constructor.resolve(callback()).then(function () {throw reason})},)}}
Promise.resolve(), Promise.reject()
Promise.resolve()/Promise.reject() 相当于调用内部的 resolve/reject 实现,直接设置状态为 FULFILLED/REJECTED,并且可以支持 then 链式调用。
Promise.resolve = function (value) {const Constructor = thisif (value && typeof value === 'object' && value.constructor === Constructor) {return value}const promise = new Constructor(internal.noop)return resolve(promise, value)}Promise.reject = function (reason) {const Constructor = thisconst promise = new Constructor(internal.noop)return reject(promise, reason)}
Promise.all(), Promise.allSettled()
Promise.all 方法接收一个 promise 的 iterable 类型的输入,resolve 回调的结果是一个数组。
在内部构造一个 Promise 实例 pp
用于返回。
依次 resolve 传入的 promise 数组,并在 then.onFulfilled 方法中将 count+1,当 count === promises.length 时,意味着所有的 promise 都 resolved 了,调用之前构造的 pp.resolve(value[])
当任何一个输入的 promise 进入 reject 回调执行或者输入不合法的 promise 就会立即抛出错误,执行 pp.reject(error)。
Promise.all = function (iterable) {const self = this// 如果参数不是数组则抛错if (!utils.isArray(iterable)) {return this.reject(new TypeError('parameter must be an array'))}const len = iterable.lengthlet called = false// 如果参数长度为0 则说明为空数组if (!len) {return this.resolve([])}const values = new Array(len) // 最后输出的值let resolved = 0 // resolve 的数量let i = 0const promise = new this(internal.noop)function allResolver(value, i) {self.resolve(value).then(function (value) {values[i] = valueif (++resolved === len && !called) {called = trueinternal.resolve(promise, values)}},function (error) {if (!called) {called = trueinternal.reject(promise, error)}},)}while (i++ < len) {allResolver(iterable[i], i)}return promise}
Promise.allSettled 与 Promise.all 类似,同样需要对内部已执行的 promise 计数。
由于规范定义方法返回一个在所有给定的 promise 都已经 fulfilled 或 rejected 后的 promise,并带有一个对象数组,每个对象表示对应的 promise 结果。
保存下来每一个 promise 的结果,并根据成功/失败标记该位置的 value.status,以及对应的值/错误 value.value,最后通过 pp.resolve(value[]) 返回所有 promise 的执行结果。
function allSettled(iterable) {const self = thisif (!utils.isArray(iterable)) {return this.reject(new TypeError('parameter must be an array'))}const len = iterable.lengthlet called = falseif (!len) {return this.resolve([])}const values = new Array(len) // 最后输出的值let settled = 0 // resolve 的数量let i = 0const promise = new this(internal.noop)function allResolver(value, i) {self.resolve(value).then(function (value) {values[i] = {status: 'fulfilled',value,}if (++settled === len && !called) {called = trueinternal.resolve(promise, values)}},function (reason) {values[i] = {status: 'rejected',reason,}if (++settled === len && !called) {called = trueinternal.resolve(promise, values)}},)}while (i++ < len) {allResolver(iterable[i], i)}return promise}
Promise.race
Promise.race(iterable)
方法构造一个 Promise 实例 pp
,一旦传入的 promises 中的某个 promise 解决或拒绝,就会立即执行 pp.resolve(value)/pp.reject(error)
。
Promise.race = function (iterable) {const self = thisif (!utils.isArray(iterable)) {return this.reject(new TypeError('parameter must be an array'))}const len = iterable.lengthlet called = falseif (!len) {return this.resolve([])}let i = 0const promise = new this(internal.noop)function resolver(value) {self.resolve(value).then(function (value) {if (!called) {called = trueinternal.resolve(promise, value)}},function (error) {if (!called) {called = trueinternal.reject(promise, error)}},)}while (i++ < len) {resolver(iterable[i])}return promise}