根据 Promises/A+ 规范实现 Promise

Promise/A+ 规范

Promises/A+ 规范(以下简称规范)可以在 Promises/A+ 查看,需要中文可以选择 AI 翻译或者 Promise/A+ 规范

简单来说,符合 Promises/A+ 规范的 promise(以下简称 promise )表示异步操作的最终结果,其应该是一个具有 then 方法的对象或者函数,该 promise 的 then 方法的行为符合规范。promise 必须处于 pendingfulfilledrejected 三种状态之一,且其状态只允许由 pending 转换为其他两种状态——即其他两种状态不允许改变。当处于 fulfilled 状态时,promise 具有一个不可修改的值,表示异步处理的结果值;当处于 rejected 状态时,promise 具有一个不可修改的拒因(reason),表示异步处理被拒绝(或者说失败)的原因

构造

    按照 ES6 中使用 Promise 的例子,此处将 Promise 定义为一个类

    1
    2
    3
    4
    class MyPromise {
    constructor() {}
    then() {}
    }

    根据规范,Promise 拥有三种状态(语意上为待定已满足被拒绝),拥有异步处理的值和拒因,则构造函数应该写为

    1
    2
    3
    4
    5
    6
    7
    8
    class MyPromise {
    constructor() {
    this.status = 'pending'
    this.value = undefined
    this.reason = undefined
    }
    then() {}
    }

    在实际使用 Promise 时,会在构造函数里传入一个接收两个函数参数的回调方法:

    1. 第一个参数方法负责将 promise 置为 fulfilled 状态
    2. 第二个参数方法负责将 promise 置为 rejected 状态

    于是改造为以下写法,其中假设 fulfillPromise 方法可以实现上述功能1、rejectPromise 方法可以实现上述功能2

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    class MyPromise {
    constructor(fn) {
    this.status = 'pending'
    this.value = undefined
    this.reason = undefined
    fn((value) => {
    fulfillPromise(this, value)
    }, (reason) => {
    rejectPromise(this, reason)
    })
    }
    then() {}
    }

    前述的 fulfillPromise 方法和 rejectPromise 方法根据规范要求,只需要将处于 pending 状态的 promise 的状态修改,并赋予对应的值或拒因即可

    考虑到实现这两个方法时需要频繁交互 promise 的状态,因此将 promise 的三种状态提出为一个映射,避免每次都手写字符串做匹配或赋值

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    const STATUS = {
    PENDING: 'pending',
    FULFILLED: 'fulfilled',
    REJECTED: 'rejected'
    }

    function fulfillPromise(promise, value) {

    if (promise.status !== STATUS.PENDING) return
    promise.status = STATUS.FULFILLED
    promise.value = value
    }

    function rejectPromise(promise, reason) {

    if (promise.status !== STATUS.PENDING) return
    promise.status = STATUS.REJECTED
    promise.reason = reason
    }

    到此,简单的 Promise 构造方法实现完毕

    then 方法

    此处需要回顾一下规范中的 then 方法

    then 方法接收两个参数:

    1
    promise2 = promise.then(onFulfilled, onRejected)
    1. 其中的 onFulfilledonRejected 都是可选参数,并且当它们各自不为函数时,会被忽略。它们各自只能在 promise 状态发生相应变化(指变为 fulfilled 时调用 onFulfilled 并传入 promise 的值;变为 rejected 时调用 onRejected 并传入 promise 的拒因)后被调用,且只能被调用一次
    2. then 方法可以被调用多次。当 promise 被满足时,按照原始调用顺序依次执行回调 onFulfilled,当 promise 被拒绝时,按照原始调用顺序依次执行回调 onRejected
    3. then 方法必须返回一个 promise

    由以上第1点得知,onFulfilledonRejected 调用时:需要判断是否为函数,不为函数则忽略——返回的新 promise 的值或拒因与当前 promise 相同;需要判断 promise 的状态,避免 promise 不为 pending 时状态发生改变

    由以上第2点可以得知,promise 需要有两个回调队列,满足依次回调 onFulfilled 或者 onRejected。而且在 fulfillPromiserejectPromise 中应该依次调用相应的回调队列

    由此得到:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    const isFunction = function(func) {
    return Object.prototype.toString.call(func).toLocaleLowerCase() === '[object function]'
    }


    const runCbs = function(cbs, value) {
    cbs.forEach((cb) => {
    cb(value)
    })
    }

    function fulfillPromise(promise, value) {

    runCbs(promise.fulfilledCallbacks, value)
    }

    function rejectPromise(promise, reason) {

    runCbs(promise.rejectedCallbacks, reason)
    }

    class MyPromise {
    constructor(fn) {

    this.fulfilledCallbacks = []
    this.rejectedCallbacks = []
    }
    then(onFulfilled, onRejected) {
    const promise1 = this
    const promise2 = new MyPromise(() => {})
    if (promise1.status === STATUS.FULFILLED) {
    if (isFunction(onFulfilled)) {

    } else {
    fulfillPromise(promise2, promise1.value)
    }
    } else if (promise1.status === STATUS.REJECTED) {
    if (isFunction(onRejected)) {

    } else {
    rejectPromise(promise2, promise1.reason)
    }
    } else {
    promise1.fulfilledCallbacks.push(() => {

    })
    promise1.rejectedCallbacks.push(() => {

    })



    }
    return promise2
    }
    }

    上述代码中的 todo: 异步调用后执行回调 意味着需要调用回调方法执行回调。规范中 2.2.4 提到,必须在执行上下文堆栈仅包含平台代码时调用 onFulfilledonRejected ,换句话说就是需要异步执行回调。这里简单使用 setTimeout 模拟一下,基本框架为:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    setTimeout(() => { 
    try {





    const x = onFulfilled(promise1.value)
    resolvePromise(promise2, x)
    } catch(error) {
    rejectPromise(promise2, error)
    }
    }, 0)

    这里的 resolvePromise 方法,表示根据回调方法的返回值,决议(resolve)一个 promise

    如果说 then 方法是一个 promise 对象的核心,那么这个 resolvePromise 方法就是 then 方法的核心,下文会提到,此处先按下不表

    于是将这个异步调用也抽象成一个 simulateAsyncCall 方法:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    function simulateAsyncCall(promise, onCall, value) {
    setTimeout(() => {
    try {
    const x = onCall(value)
    resolvePromise(promise, x)
    } catch (e) {
    rejectPromise(promise, e)
    }
    }, 0)
    }

    然后补全 todo 处的代码为:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    then(onFulfilled, onRejected) {
    const promise1 = this
    const promise2 = new MyPromise(() => {})
    if (promise1.status === STATUS.FULFILLED) {
    if (isFunction(onFulfilled)) {
    simulateAsyncCall(promise2, onFulfilled, promise1.value)
    } else {
    fulfillPromise(promise2, promise1.value)
    }
    } else if (promise1.status === STATUS.REJECTED) {
    if (isFunction(onRejected)) {
    simulateAsyncCall(promise2, onRejected, promise1.reason)
    } else {
    rejectPromise(promise2, promise1.reason)
    }
    } else {


    onFulfilled = isFunction(onFulfilled) ? onFulfilled : (value) => { return value }
    onRejected = isFunction(onRejected) ? onRejected : (err) => { throw err }
    promise1.fulfilledCallbacks.push((value) => {
    simulateAsyncCall(promise2, onFulfilled, value)
    })
    promise2.rejectedCallbacks.push((reason) => {
    simulateAsyncCall(promise2, onRejected, reason)
    })
    }
    return promise2
    }
    此时的完整代码
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132

    const STATUS = Object.freeze({
    PENDING: 'pending',
    FULFILLED: 'fulfilled',
    REJECTED: 'rejected'
    })

    const isFunction = function (func) {
    return Object.prototype.toString.call(func).toLocaleLowerCase() === '[object function]'
    }

    const runCallbacks = function (cbs, value) {

    cbs.forEach((cb) => cb(value))
    }






    const fulfillPromise = function (promise, value) {

    if (promise.status !== STATUS.PENDING) return
    promise.value = value
    promise.status = STATUS.FULFILLED
    runCallbacks(promise.fulfilledCallbacks, value)
    }







    const rejectPromise = function (promise, reason) {

    if (promise.status !== STATUS.PENDING) return
    promise.status = STATUS.REJECTED
    promise.reason = reason
    runCallbacks(promise.rejectedCallbacks, reason)
    }






    const resolvePromise = function(promise, value) {}







    const simulateAsyncCall = function (promise, func, value) {
    setTimeout(() => {
    try {
    const x = func(value)
    resolvePromise(promise, x)
    } catch (e) {

    rejectPromise(promise, e)
    }
    }, 0)
    }

    class MyPromise {
    constructor(fn) {
    this.status = STATUS.PENDING
    this.value = undefined
    this.reason = undefined
    this.fulfilledCallbacks = []
    this.rejectedCallbacks = []
    fn(
    (value) => {
    resolvePromise(this, value)
    },
    (reason) => {
    rejectPromise(this, reason)
    }
    )
    }
    then(onFulfilled, onRejected) {
    const promise1 = this
    const promise2 = new MyPromise(() => {})
    if (promise1.status === STATUS.FULFILLED) {
    if (isFunction(onFulfilled)) {
    simulateAsyncCall(promise2, onFulfilled, promise1.value)
    } else {




    fulfillPromise(promise2, promise1.value)
    }
    } else if (promise1.status === STATUS.REJECTED) {
    if (isFunction(onRejected)) {
    simulateAsyncCall(promise2, onRejected, promise1.reason)
    } else {




    rejectPromise(promise2, promise1.reason)
    }
    } else {


    onFulfilled = isFunction(onFulfilled)
    ? onFulfilled
    : (value) => {
    return value
    }
    onRejected = isFunction(onRejected)
    ? onRejected
    : (err) => {
    throw err
    }

    promise1.fulfilledCallbacks.push((value) => {
    simulateAsyncCall(promise2, onFulfilled, value)
    })
    promise1.rejectedCallbacks.push((reason) => {
    simulateAsyncCall(promise2, onRejected, reason)
    })
    }

    return promise2
    }
    }

    resolve:决议 promise

    实现 resolve 之前,先要引入一个之前用不到的术语(Terminology)——thenable

    thenable 是一个定义 then 方法的对象或函数

    听起来很像 promise?没错,按照规范的说法,这个 thenable 对象至少有点像 promise(at least somewhat like a promise)

    规范区分 promise 和 thenable 想来是为了兼容不规范的 promise 实现

    规范用了大篇幅介绍决议过程。决议过程将 promise 和 一个 x 值作为输入,因此可以假设决议方法 resolvePromise 的定义为:

    1
    const resolvePromise = function(promise, x) {}

    决议时,执行以下逻辑:

    1. 如果 promise 和 x 是同一个值,则以一个 TypeError 拒绝 promise
    2. 如果 x 是另一个 promise,则使用 x 的状态决议 promise
    3. 如果 x 是一个对象或者函数,则尝试读取 x.then
      • 若读到的不是方法则以 x 为值满足 promise
      • 若是则以 x 为 this 调用它,传入与 promise 的 then 方法相似的两个回调函数,只不过这两个回调函数会调用 resolvePromiserejectPromise 来处理 promise 的状态
      • 调用 x.then 时如果多次调用 resolvePromise 或者 rejectPromise 则应该只执行第一次调用而忽略后续调用
    4. 如果 x 不是对象也不是函数,则以 x 为值满足 promise
    5. 决议中的任何异常均会导致 promise 被以该异常拒绝(前提是 promise 仍处于 pending 状态)

    对于第2点,所谓的使用 x 的状态决议 promise, 即指 x 已满足则 promise 也被满足, x 被拒绝则 promise 也被拒绝,x 待定则 promise 也待定,直到 x 被满足或者被拒绝。这个过程是很符合直觉的

    对于第3点中的 x.then 是一个方法的情况,其实按照规范描述写代码可能会更直接一些

    由此可以得到代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    const isObject = function(obj) {
    return Object.prototype.toString.call(obj).toLocaleLowerCase() === '[object object]'
    }

    const isPromise = function (promise) {
    return promise instanceof MyPromise
    }

    const resolvePromise = function (promise, x) {

    if (x === promise) {
    rejectPromise(promise, new TypeError('resolving promise must not use the same promise'))
    } else if (isPromise(x)) {

    if (x.status === STATUS.FULFILLED) {

    fulfillPromise(promise, x.value)
    } else if (x.status === STATUS.REJECTED) {

    rejectPromise(promise, x.reason)
    } else {

    x.then(
    (v) => {

    fulfillPromise(promise, v)
    },
    (r) => {

    rejectPromise(promise, r)
    }
    )
    }
    } else if (isObject(x) || isFunction(x)) {

    let then
    let called = false
    try {
    then = x.then
    } catch (e) {

    rejectPromise(promise, e)
    return
    }

    if (isFunction(then)) {
    try {

    then.call(
    x,
    (v) => {

    if (!called) {
    called = true
    resolvePromise(promise, v)
    }
    },
    (r) => {

    if (!called) {
    called = true
    rejectPromise(promise, r)
    }
    }
    )
    } catch (e) {

    if (!called) {
    called = true
    rejectPromise(promise, e)
    }
    }
    } else {

    fulfillPromise(promise, x)
    }
    } else {

    fulfillPromise(promise, x)
    }
    }

    测试

    Github 上的 Promises/A+ 除了提供了 Promises/A+ 规范以外,还提供了测试自定义的 Promise 的方法

    全局安装命令:

    1
    npm install promises-aplus-tests -g

    局部安装可以参见上述仓库,这里不再赘述

    安装完成后,对自定义的 Promise 文件增加如下修改:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    MyPromise.deferred = function () {
    const deferred = {}
    deferred.promise = new MyPromise((resolve, reject) => {
    deferred.resolve = resolve
    deferred.reject = reject
    })
    return deferred
    }

    module.exports = MyPromise

    接着运行以下命令即可看到测试结果:

    1
    promises-aplus-tests promise.js

    修正

    可以看到有一些用例,没有通过。最终排查到的结果,是在构造 Promise 执行传入的同步方法时,调用了 fulfillPromise 而不是 resolvePromise 方法,改动如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    // @other code
    class MyPromise {
    constructor(fn) {
    this.status = STATUS.PENDING
    this.value = undefined
    this.reason = undefined
    this.fulfilledCallbacks = []
    this.rejectedCallbacks = []
    fn(
    (value) => {
    - fulfillPromise(this, value)
    + resolvePromise(this, value)
    },
    (reason) => {
    rejectPromise(this, reason)
    }
    )
    }
    // @other code
    }

    当控制台输出 872 passing 即表示所有用例通过,当前的自定义 Promise 完整实现了 Promises/A+ 规范

    all tests passing

    完善

    isFunctionisObject 方法均使用了 Object.prototype.toString.call() 这样的判断类型的方式,由此可以再抽象出一个 isType 方法,并将 isFunctionisObject 做如下改造:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    const isType = function(type) {
    return function(obj) {
    return Object.prototype.toString.call(obj).toLocaleLowerCase() === '[object ' + type + ']'
    }
    }

    const isObject = isType('object')

    const isFunction = isType('function')

    最终的 promise.js 文件内容为:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
    200
    201
    202
    203
    204
    205
    206
    207
    208
    209
    210
    211
    212
    213
    214
    215
    216
    217
    218
    219
    220
    221
    222
    223
    224
    225

    const STATUS = Object.freeze({
    PENDING: 'pending',
    FULFILLED: 'fulfilled',
    REJECTED: 'rejected'
    })

    const isType = function (type) {
    return function (obj) {
    return Object.prototype.toString.call(obj).toLocaleLowerCase() === '[object ' + type + ']'
    }
    }

    const isObject = isType('object')

    const isFunction = isType('function')

    const isPromise = function (promise) {
    return promise instanceof MyPromise
    }

    const runCallbacks = function (cbs, value) {

    cbs.forEach((cb) => cb(value))
    }






    const fulfillPromise = function (promise, value) {

    if (promise.status !== STATUS.PENDING) return
    promise.value = value
    promise.status = STATUS.FULFILLED
    runCallbacks(promise.fulfilledCallbacks, value)
    }







    const rejectPromise = function (promise, reason) {

    if (promise.status !== STATUS.PENDING) return
    promise.status = STATUS.REJECTED
    promise.reason = reason
    runCallbacks(promise.rejectedCallbacks, reason)
    }






    const resolvePromise = function (promise, x) {

    if (x === promise) {
    rejectPromise(promise, new TypeError('resolving promise must not use the same promise'))
    } else if (isPromise(x)) {

    if (x.status === STATUS.FULFILLED) {

    fulfillPromise(promise, x.value)
    } else if (x.status === STATUS.REJECTED) {

    rejectPromise(promise, x.reason)
    } else {

    x.then(
    (v) => {

    fulfillPromise(promise, v)
    },
    (r) => {

    rejectPromise(promise, r)
    }
    )
    }
    } else if (isObject(x) || isFunction(x)) {

    let then
    let called = false
    try {
    then = x.then
    } catch (e) {

    rejectPromise(promise, e)
    return
    }

    if (isFunction(then)) {
    try {

    then.call(
    x,
    (v) => {

    if (!called) {
    called = true
    resolvePromise(promise, v)
    }
    },
    (r) => {

    if (!called) {
    called = true
    rejectPromise(promise, r)
    }
    }
    )
    } catch (e) {

    if (!called) {
    called = true
    rejectPromise(promise, e)
    }
    }
    } else {

    fulfillPromise(promise, x)
    }
    } else {

    fulfillPromise(promise, x)
    }
    }







    const simulateAsyncCall = function (promise, func, value) {
    setTimeout(() => {
    try {
    const x = func(value)
    resolvePromise(promise, x)
    } catch (e) {

    rejectPromise(promise, e)
    }
    }, 0)
    }

    class MyPromise {
    constructor(fn) {
    this.status = STATUS.PENDING
    this.value = undefined
    this.reason = undefined
    this.fulfilledCallbacks = []
    this.rejectedCallbacks = []
    fn(
    (value) => {
    resolvePromise(this, value)
    },
    (reason) => {
    rejectPromise(this, reason)
    }
    )
    }
    then(onFulfilled, onRejected) {
    const promise1 = this
    const promise2 = new MyPromise(() => {})
    if (promise1.status === STATUS.FULFILLED) {
    if (isFunction(onFulfilled)) {
    simulateAsyncCall(promise2, onFulfilled, promise1.value)
    } else {




    fulfillPromise(promise2, promise1.value)
    }
    } else if (promise1.status === STATUS.REJECTED) {
    if (isFunction(onRejected)) {
    simulateAsyncCall(promise2, onRejected, promise1.reason)
    } else {




    rejectPromise(promise2, promise1.reason)
    }
    } else {


    onFulfilled = isFunction(onFulfilled)
    ? onFulfilled
    : (value) => {
    return value
    }
    onRejected = isFunction(onRejected)
    ? onRejected
    : (err) => {
    throw err
    }

    promise1.fulfilledCallbacks.push((value) => {
    simulateAsyncCall(promise2, onFulfilled, value)
    })
    promise1.rejectedCallbacks.push((reason) => {
    simulateAsyncCall(promise2, onRejected, reason)
    })
    }

    return promise2
    }
    }

    MyPromise.deferred = function () {
    const deferred = {}
    deferred.promise = new MyPromise((resolve, reject) => {
    deferred.resolve = resolve
    deferred.reject = reject
    })
    return deferred
    }

    module.exports = MyPromise