自己鼓捣一个Promise

8次阅读

共计 8928 个字符,预计需要花费 23 分钟才能阅读完成。

源码
very-simple-promise,包含了不完善的单元测试❤️.
感谢
代码君的自白
这篇的文章主要参考了上面的博客,谢谢他的帮助????。
Promise/A+ 规范
Promises/A+ 规范, 下面???? 是对规范的内容的部分翻译。英文比较烂,不喜欢的可以不看。
承诺必须满足三种状态, pending(等处理), fulfilled(履行), rejected(拒绝)
Promises 的状态

promise 处于 pending 时
可以过渡到 fulfilled 或者 rejected 状态

promise 处于 fulfilled 时
不能向其他状态过渡

promise 处于 rejected 时
不能向其他状态过渡

then
promise 的 then 方法接受两个参数
promise.then(onFulfilled, onRejected)

onFulfilled 和 onRejected 都是可选参数

onFulfilled 不是函数需要忽略
onRejected 不是函数需要忽略

onFulfilled 如果是一个函数

onFulfilled 在 promise 状态为 fulfilled 时被调用
onFulfilled 只能被调用一次
onFulfilled 不能在 fulfilled 之前不能调用

onRejected 如果是一个函数

onRejected 在 promise 状态为 rejected 时被调用
在 rejected 之前不能调用它
onRejected 只能被调用一次

onFulfilled or onRejected must not be called until the execution context stack contains only platform code. (原文不太懂什么意思,但是规范给出了注解)
onFulfilled 和 onRejected 必须作为函数调用

then 可以在同一个 promise 多次调用

如果 promise 被履行, 则 onFulfilled 回调必须按照 then 的顺序执行
如果 promise 被拒绝, 则 onRejected 回调必须按照 then 的顺序执行

promise2 = promise1.then(onFulfilled, onRejected);

then 必须返回 promise

如果 onFulfilled 或者 onRejected 返回一个值,则运行处理 Promise 的过程 [[Resolve]](promise2, x)(这里处理的 Resolve(promise2, x), onFulfilled 或者 onRejected 的返回值会被 resolve 处理, resolve 的第一个参数是 then 返回的新的 promise)
如果 onFulfilled, onRejected 抛出错误 error, 则 promise2 必须为 rejected
如果 onFulfilled 不是一个函数, 而 promise1 的状态为 fulfilled, promise2 必须与 promise1 具有相同的值被履行
如果 onRejected 不是一个函数, 而 promise1 的状态为 rejected, promise2 必须与 promise1 具有相同的值被拒绝

处理 Promise
[[Resolve]](promise, x) 需要遵循以下规范

如果 promise 与 x 相等, 抛出 TypeError 的错误, 并以错误为理由拒绝 promise

如果 x 是 Promise

如果 x 处于 pending,promise 也需要保持 pending,直到 x 接受 pending
如果 x 处于 fulfilled,promise 需要使用相同值执行 promise
如果 x 处于 rejected,promise 需要使用相同的值拒绝 promise

如果 x 是对象或者函数

将 x.then 赋于 then
如果获取 x.then 时抛出错误,则使用错误作为理由拒绝 promise

如果 then 是函数????️

使用 x 作为 then 函数的作用域中的 this, 传入两个参数作为回调,分别是 resolvePromise, 和 rejectPromise
如果 resolvePromise 如果以 y 为参数运行,则运行 [[Resolve]](promise, y), 履行 promise。resolve(promise,y)
如果 rejectPromise 以 r 为参数运行,则以 r 为原因拒绝 promise。reject(promise, r)

如果 then 抛出了错误

如果 resolvePromise,rejectPromise 已经调用对错误忽略
如果没有调用,用错误作为原因拒绝 promise

如果 then 不是函数,使用 x 作为参数执行 promise

如果 x 不是对象也不是函数,则使用 x 作为参数执行 promise

注解
onFulfilled 和 onRejected 方法应当是异步执行,且应该在 then 方法被调用的那一轮事件循环之后的微任务执行栈中执行。可以使用宏任务 macrotask 和微任务 microtask 机制实现。也就是说宏任务完成后 (Promise 的 then 调用后),会清空微任务队列 (执行 onFulfilled 或者 onRejected)。
macrotask 包含:script(整体代码)、setTimeout、setInterval、I/O、UI 交互事件、postMessage、requestAnimationFrame、MessageChannel、etImmediate(Node.js 环境)
microtask 包含:Promise.then、setImmediate、MutaionObserver、process.nextTick(Node.js 环境)
事件循环
下面???? 是一个关于事件循环的例子????

Promise 中的参数函数,应当是立即调用的。我们这时先执行了 resolve(1), 这会将外层的 Promise 的状态置为 fulfilled 态。但是这时外层的 then 还没有执行。
我们接下来执行了 Promise.resolve().then(() => console.log(2)), 我们将 then 的 onFulfilled,push 到了 macrotask 队列中,会在宏任务执行完成后清空微任务。
执行外层 Promise 的 then, onFulfilled, push 到了 macrotask 队列中。
接着执行 console.log(3), 然后清空 macrotask 队列,执行 2,1(队列先进先出)
Promise 构造函数

const pending = 0
const fulfilled = 1
const rejected = 2

function reject (promise, result) {}

function resolve (promise, reason) {}

export default class Promise {
constructor (fn) {
this.fn = fn
// Promise 的状态, 初始状态为 pending
this._state = pending
// Promise 的 fulfilled 态的原因,或者 Promise 的 rejected 态的理由
this._value = null
// 存储 then 的队列
this._tasks = []
// 创建 Promise 对象后,立即执行 fn
this.init()
}

init () {
try {
// 执行 fn, 传入包装后的 resolve,reject
// reject,resolve 会修改 promise 的状态
this.fn(
(result) => resolve(this, result),
(reason) => reject(this, reason)
)
} catch (error) {
reject(this)
}
}
}
根据规范 2.2.6: then may be called multiple times on the same promisethen 可以在同一个 promise 中多次调用。所以我们用一个数组存储 then 的结果。
Promise.prototype.then
根据规范 2.2.1: A promise’s then method accepts two arguments. Both onFulfilled and onRejected are optional arguments。then 接受两个参数, 两个参数可选。
根据规范 2.2.1.1, 2.2.1.2: If onFulfilled is not a function, it must be ignored. If onRejected is not a function, it must be ignored.。onFulfilled, onRejected 必须是函数负责会被忽略。
根据规范 2.2.2, 2.2.3, onFulfilled 必须在状态为 fulfilled 调用, onRejected 必须在状态为 rejected 调用
根据规范 2.2.7: then must return a promise。then 必须返回一个 promise。
????️ 为什么不能返回当前的 promise? 因为根据规范 2.1.2, 2.1.3, promise 被修改状态后,不能再次修改状态。我们如果需要执行 then 的 callback 需要修改 promise 的状态。
我们使用 task 封装????then 的回调以及 then 返回的 promise。

class Task {
constructor (onFulfilled, onRejected, promise) {
if (typeof onFulfilled !== ‘function’) {
onFulfilled = null
}
if (typeof onRejected !== ‘function’) {
onRejected = null
}
this.onFulfilled = onFulfilled
this.onRejected = onRejected
this.promise = promise
}
}

class Promise {

// …

then (onFulfilled, onRejected) {

let nextPromise = new Promise(function () {})

let task = new Task(onFulfilled, onRejected, nextPromise)

if (this._state === pending) {
this._tasks.push(task)
} else {
handlePromise(this, task)
}

// 返回新的 promise
return nextPromise
}
}

Promise.prototype.catch
catch 函数同样会返回新的 promise, 但是在创建 task 时,我们不会传入 onFulfilled 参数。所以当 promise 当为 fulfilled 态,虽然 catch 的回调同样存放_task 中,但是由于 callback 为 null, 在 handlePromise 中会向下穿透。
class Promise {
// …
catch (onRejected) {
let nextPromise = new Promise(function () {})

// onFulfilled 设置为 null
let task = new Task(null, onRejected, nextPromise)

if (this._state === pending) {
this._tasks.push(task)
} else {
handlePromise(this, task)
}

// 返回新的 promise
return nextPromise
}
}
Promise.prototype.finally
finally 方法用于指定不管 Promise 对象最后状态如何,都会执行的操作。
我们无论 promise 的状态如何,都在 promise 的最后面添加 then,并传入了 onFulfilled, onRejected 两个回调。在回调中,我们执行 finally 的 callback 参数。这样无论之前的 promise 是 fulfilled 态,还是 rejected 态,都会执行 finally 添加的参数。

class Promise {
// …

finally (callback) {
// this 指向调用 finally 的对象
const self = this
// 向 Promise 链中添加 then,无论,promise 是 resolve 态还是 reject 态,都会执行 callback
// 并且会通过 then,继续将 result 或 reason 向下传递
return self.then(
result => Promise.resolve(callback()).then(_ => result),
reason => Promise.resolve(callback()).then(_ => { throw reason})
)
}
}
resolve
resolve 用来修改 promise 状态,将 promise 状态设置为 fulfilled 态, 并执行 then 的 onFulfilled 回调
根据规范 2.3.1: If promise and x refer to the same object, reject promise with a TypeError as the reason. 如果 promise 与 x 相等,我们使用 TypeError 的错误拒绝 promise
根据规范 2.3.2。如果 result 是 promise,并且处于 pending 态,promise 需要保持 pending 态,直到 result 被执行和拒绝后,我们使用 result 的状态履行或者拒绝 promise。如果 result 是 promise,并且处于 fulfilled 或 rejected 态,我们使用 result 的状态拒绝或者履行 promise。
根据规范 2.3.3, 我们判断 result 是否为一个 Object。如果 result 为 Object, 我们则取出它的 then 的属性, 判断 then 属性是否为 Function, 如果 then 为 Function, 我们设置 then 的作用域的 this 指向 result, 我们传入 resolvePromise, rejectPromise 作为参数。
根据规范 2.3.4: If x is not an object or function, fulfill promise with x, 如果 x 不是函数或者对象,我们用 result 结果作为参数执行 promise。

function resolve (promise, result) {
if (promise === result) {
throw reject(promise, new TypeError(‘promise and x refer to the same object’))
}

if (isPromise(result)) {
if (result._state === pending) {
result._tasks.concat(promise._tasks)
} else if (result._state === fulfilled || result._state === rejected) {
let task
while (task = promise._tasks.shift()) {
handlePromise(result, task)
}
}
return
}

if (isObject(result)) {

let then = null

try {
then = result.then
} catch (error) {
reject(promise, error)
}

if (isFunction(then)) {
try {
let resolvePromise = function (y) {
resolve(promise, y)
}
let rejectPromise = function (r) {
reject(promise, r)
}
then.call(result, resolvePromise, rejectPromise)
} catch (error) {
reject(promise, error)
}

return
}
}

promise._state = fulfilled
promise._value = result

if (promise._tasks && promise._tasks.length) {
let task = null
while (task = promise._tasks.shift()) {
handlePromise(promise, task)
}
}
}
reject
reject 将 promise 的状态设置为 rejected, 并以当前的 promise 的状态,执行 promise 中通过 then 注册的 onRejected 回调。

function reject (promise, reason) {
if (promise._state !== pending) {
return
}

promise._state = rejected
promise._value = reason

let task
while (task = promise._tasks.shift()) {
handlePromise(promise, task)
}
}
handlePromise
handlePromise 函数主要根据当前的 promise 的状态, 以及内容 (resolve 或者 reject 的参数)。处理通过 then 注册的回调。并且会链式的调用,注册在 then 返回的新 promise 的上的 then 的回调

// 将回调的结果,传入第二个 then 中
fn().then().then()
根据规范 2.2.4, 以及规范给出的注解。当 promise 的状态改变,onFulfilled, onRejected 并不会立即执行,而且在本次的宏任务完成后,才会执行 onFulfilled 或者 onRejected。而 setImmediate 则是将代码 push 到微任务队列中。在宏任务中会清空微任务队列。

function handlePromise (prevPromise, task) {
// 需要在宏任务完后的微任务队列中执行
setImmediate(() => {
// nextPromise 是 then 返回的 promise
const {onFulfilled, onRejected, promise: nextPromise} = task
let callback = null

let value = prevPromise._value
let state = prevPromise._state

if (state === fulfilled) {
callback = onFulfilled
} else if (state === rejected) {
callback = onRejected
}

if (!callback) {
// 如果在 promise 中没有注册 callback
if (state === fulfilled) {
resolve(nextPromise, value)
} else if (state === rejected) {
reject(nextPromise, value)
}
} else {
try {
const result = callback(value)
// 对 then 中返回 promise 处理
// 将 callback 返回的结果,带入到新的 promise 中
resolve(nextPromise, result)
} catch (error) {
reject(nextPromise, error)
}
}
})
}
Promise.resolve & Promise.reject
Promise.resolve 方法返回一个新的 Promise 对象,状态为 resolved。Promise.reject(reason) 方法也会返回一个新的 Promise 实例,该实例的状态为 rejected。
class Promise {

// …

static resolve (result) {
return new Promise((resolve) => {resolve(result) })
}

static reject (reason) {
return new Promise((_, reject) => {reject(reason) })
}
}
Promise.all && Promise.race
Promise.all 和 Promise.race 必须接受一个数组为参数,数组中为多个 Promise 的实例。Promise.all 和 Promise.race 的使用我就不再这里赘述了。
Promise.all 会使用计数器,记录 Promise 数组中的所有 Promise 实例的状态是否都变为 fulfilled 态,如果计数器的长度和数组长度一致,我们则会将 Promise.all 的状态设置为 fulfilled 态。

class Promise {
static race (promises) {
if (isArray(promises)) {
let promisesLength = promises.length
return new Promise((resolve, reject) => {
for (let i = 0; i < promisesLength; i++) {
promises[i].then((result) => {
resolve(result)
}).catch((error) => {
reject(error)
})
}
})
} else {
throw new TypeError(‘The arguments must be arrays’)
}
}

static all (promises) {
if (isArray(promises)) {
let promisesLength = promises.length
let counter = 0
let resultList = []
return new Promise((resolve, reject) => {
for (let i = 0; i < promisesLength; i++) {
promises[i].then((result) => {
counter += 1
resultList.push(result)
if (counter === promisesLength) {
resolve(resultList)
}
}).catch((error) => {
reject(error)
})
}
})
} else {
throw new TypeError(‘The arguments must be arrays’)
}
}
}
其他
VueRouter 源码分析
Preact 源码分析

正文完
 0