# 实现一个 Promise（2）

上一篇实现了 Promise 的核心方法 `then()`，本篇将在此基础上实现 Promise 的其它 instance method（实例方法）和 static method（静态方法）。

说明：

* 在基于 `then()` 实现其它方法时，选择了用 `then-catch-finally` 的形式，主要是为了代码的可读性。理论上，底层库更应该用 `then()` 的双参数形式，毕竟会少返回一两层 promise。
* 这里就不考虑不同的 Promise 实现之间的 interoperability（互操作）了，比如在 `resolve()` 里直接判断 `x instanceof MyPromise`，比如在其它方法里直接调用 `MyPromise.resolve()`。毕竟 Promise 已经成为语言的正式标准也有七八年了，再加上本文的主要目的是想“通过自己实现一个 Promise 来更好地理解其原理”。

## 1. 实例方法

`then()` 是 Promise 的 primitive（原始）方法，`catch()` 和 `finally()` 都是基于它实现的。

### 1.1 `Promise.prototype.then()`

```javascript
MyPromise.prototype.then = function (onFulfilled, onRejected) {
    // ...
}
```

具体的代码实现，详见上一篇：[实现一个符合 Promises/A+ 规范的 Promise](/web/js/runtime/promise/implement-then-method.md)。

### 1.2 `Promise.prototype.catch()`

相当于 `then()` 的简写，代码如下：

```javascript
MyPromise.prototype.catch = function (onRejected) {
    return this.then(undefined, onRejected)
}
```

### 1.3 `Promise.prototype.finally()`

是在 `then()` 的基础上实现的，也返回一个 promise（便于链式调用），不过逻辑上有以下不同：

1. 只有一个回调参数，可以理解为形如 `then(onFinally, onFinally)`
2. 调用时机：无论是 fulfilled 还是 rejected，都会被调用（因此通常用来处理二者的公共逻辑）
3. 在调用链上，如何从前面接值：该回调函数没有参数，即不接收上一个 `then` 的执行结果
4. 在调用链上，如何向后面传值：
   * 如果回调函数的执行正常，则单纯透传上一个 `then` 的执行结果（包括 fulfilled 和 rejected）
   * 如果回调函数的执行被 reject 了，则它会把自身的异常结果传下去，即触发下一个 `then` 的 reject 逻辑

可以运行以下代码，感受 `finally()` 的用法：

```javascript
new Promise((resolve, reject) => {
    resolve(1)
    // reject(2)
}).finally((value) => {
    // 1. 始终输出 undefined，因为它的回调函数没有参数
    console.log('finally() value=', value)
    // 2. 若是正常结果，则此返回没人理，因为逻辑上是透传上一个的结果
    return 3
    // 3. 若是有异常了，则会把该异常结果传下去（此时就变成上一个的结果没人理了）
    // return Promise.reject('finally reject()')
    // throw Error('finally Error()')
}).then((value) => {
    console.log('value: ', value)
}, (reason) => {
    console.log('reason:', reason)
})
```

`finally()` 的代码示意，如下：

```javascript
MyPromise.prototype.finally = function (cb) {
    // 若构造函数未知，则可通过 this.constructor 获取
    //     在这个例子里，构造函数就是 MyPromise
    // 为了兼容 `cb()` 返回值的各种情况（同步or异步）
    //     统一将其处理成 Promise，等其执行完了再返回
    return this.then(
        (value) => MyPromise.resolve(cb()).then(() => value),
        (reason) => MyPromise.resolve(cb()).then(() => { throw reason })
    )
}
```

注意：`finally()` 的执行顺序取决于它在 `then()` 链式调用里的顺序，而并不是要“最后”执行。比如：

```javascript
new Promise((resolve, reject) => {
    resolve()
}).finally(() => {
    console.log('finally1')
}).then(() => {
    console.log('then')
}).finally(() => {
    console.log('finally2')
})
// 会输出：
// finally1
// then
// finally2
```

结合 `finally` 函数的功能语义，感觉它叫 `settled` 会更合适。但是考虑到它的使用场景，通常是处理一些不论是 fulfilled 还是 rejected 的通用逻辑，因此大多数情况都是放在链式调用的最后；即便是在中间位置，也是对上一个 `then` 的 finally 处理。站在这个角度理解，名字叫 `finally` 也说得通。

## 2. 静态方法

### 2.1 `Promise.resolve()`

要么返回一个<mark style="color:green;">新</mark>的 promise，要么返回<mark style="color:green;">参数</mark>本身。

代码示意，如下：

```javascript
MyPromise.resolve = function (x) {
    return (x instanceof MyPromise) ? x : (new MyPromise((resolve) => resolve(x)))
}
```

通常，当我们不知道某个值是不是 promise 时，就可以用 `Promise.resolve(x)` 将其显式地转为 promise。

### 2.2 `Promise.reject()`

返回一个<mark style="color:green;">新</mark>的 promise 对象，用给定的 reason 来 reject。

```javascript
MyPromise.reject = function (r) {
    return new MyPromise((undefined, reject) => { reject(r) })
}
```

## 3. 静态方法之并发

> async task concurrency，异步任务并发

JavaScript 本质上是 single-threaded（单线程的），因此在给定的瞬间只有一个 task 被执行，虽然看起来是并发的。JavaScript 中的 parallel execution（并行执行）只能通过 worker threads（工作线程）来实现。

Promise 类提供了 4 种 static method 来处理异步任务并发，包括：

1. `Promise.race()` ES2015，ES6
2. `Promise.all()` ES2015，ES6
3. `Promise.allSettled()` ES2020
4. `Promise.any()` ES2021

它们都接收一个 iterable promises（准确地说是 thenables），然后返回一个新的 promise。其中，iterable 可能包含一个或多个 non-promise value 或已经 settled 的 promise。

### 3.0 参数说明

#### （1）参数必须是 iterable

判断一个值是不是 iterable（可迭代的），代码如下：

```javascript
const isIterable = function (x) {
    if (x !== undefined && x !== null && typeof x[Symbol.iterator] === 'function') {
        return true
    } else {
        throw new TypeError(`${typeof x} ${x} is not iterable (cannot read property Symbol(Symbol.iterator))`)
    }
}
```

内置的[可迭代对象](/web/js/data-type/object/iterable.md)有 `String`, `Array`, `TypedArray`, `Map`, `Set`, `Segments` 以及 `arguments`, `NodeList` 和 `function*`, `async function*`。

当参数不是 iterable（可迭代的）时，会报错。如下：

```log
rejected: TypeError: undefined is not iterable (cannot read property Symbol(Symbol.iterator))
rejected: TypeError: object null is not iterable (cannot read property Symbol(Symbol.iterator))
rejected: TypeError: boolean true is not iterable (cannot read property Symbol(Symbol.iterator))
rejected: TypeError: boolean false is not iterable (cannot read property Symbol(Symbol.iterator))
rejected: TypeError: number 5 is not iterable (cannot read property Symbol(Symbol.iterator))
rejected: TypeError: object is not iterable (cannot read property Symbol(Symbol.iterator))
```

#### （2）当参数是 empty 时

当参数是 empty iterable 时，不同方法的处理结果不同。如下：

<table><thead><tr><th width="169.33333333333331">静态方法</th><th width="92">iterable</th><th>empty iterable 时返回的 promise</th></tr></thead><tbody><tr><td><code>race()</code></td><td>必须</td><td>永远 pending</td></tr><tr><td><code>all()</code></td><td>必须</td><td>fulfilled <code>[]</code></td></tr><tr><td><code>allSettled()</code></td><td>必须</td><td>fulfilled <code>[]</code></td></tr><tr><td><code>any()</code></td><td>必须</td><td>rejected <br><code>AggregateError: All promises were rejected</code></td></tr></tbody></table>

#### （3）当包含 non-promise value 时

参数里可能包含 non-promise value，所以统一用 `Promise.resolve(p)` 处理成 promise。

#### （4）当包含已经 settled 的 promise 时

对于参数里已经 settled 的 promise，依然按照异步的逻辑执行，即不做特殊处理。

不是 pending 状态的 promise，就称它已经 settled 了，既可以 fulfilled 也可以 rejected。

### 3.1 `Promise.race()`

返回一个<mark style="color:green;">新</mark>的 promise，用<mark style="color:green;">最先</mark> settled 的结果来 settle。

代码示意，如下：

```javascript
MyPromise.race = function (promises) {
    return new MyPromise((resolve, reject) => {
        // must be iterable promises
        if (isIterable(promises)) {
            for (let p of promises) {
                MyPromise.resolve(p).then(resolve, reject)
            }
        }
    })
}
```

### 3.2 `Promise.all()`

返回一个<mark style="color:green;">新</mark>的 promise，要么是用<mark style="color:green;">所有</mark> values 的数组来 fulfill，要么是用<mark style="color:green;">最先</mark>到的 reason 来 reject。

代码示意，如下：

```javascript
MyPromise.all = function (promises) {
    return new MyPromise((resolve, reject) => {
        // must be iterable promises
        if (isIterable(promises)) {
            const len = promises.length

            // empty iterable
            if (len === 0) {
                resolve([])
            }

            // iterable
            let result = new Array(len)
            let count = 0
            for (let i in promises) {
                MyPromise.resolve(promises[i])
                    .then((value) => {
                        count++
                        result[i] = value
                        if (count === len) {
                            resolve(result)
                        }
                    }).catch(reject)
            }
        }
    })
}
```

### 3.3 `Promise.allSettled()`

返回一个<mark style="color:green;">新</mark>的 promise，用一个 promise state snapshots 数组来 fulfill，且是在<mark style="color:green;">所有</mark>原始 promises 都 settled 了（可以 fulfilled 也可以 rejected）。

代码示意，如下：

```javascript
MyPromise.allSettled = function (promises) {
    return new MyPromise((resolve, reject) => {
        // must be iterable promises
        if (isIterable(promises)) {
            const len = promises.length

            // empty iterable
            if (len === 0) {
                resolve([])
            }

            // iterable
            let result = new Array(len)
            let count = 0
            for (let i in promises) {
                MyPromise.resolve(promises[i])
                    .then((value) => {
                        result[i] = {
                            status: 'fulfilled',
                            value
                        }
                    })
                    .catch((reason) => {
                        result[i] = {
                            status: 'rejected',
                            reason
                        }
                    })
                    .finally(() => {
                        count++
                        if (count === len) {
                            resolve(result)
                        }
                    })
            }
        }
    })
}
```

### 3.4 `Promise.any()`

返回一个<mark style="color:green;">新</mark>的 promise，要么是用<mark style="color:green;">最先</mark> fulfilled promise 来 fulfill，要么是用包含<mark style="color:green;">所有</mark> reasons 的聚合错误来 reject。

代码示意，如下：

```javascript
MyPromise.any = function (promises) {
    return new MyPromise((resolve, reject) => {
        // must be iterable promises
        if (isIterable(promises)) {
            const len = promises.length

            // empty iterable
            if (len === 0) {
                throw new AggregateError([], 'empty promises')
            }

            // iterable
            let errors = new Array(len)
            let errorCount = 0
            for (let i in promises) {
                MyPromise.resolve(promises[i])
                    .then((value) => resolve(value))
                    .catch((reason) => {
                        errorCount++
                        errors[i] = reason
                        if (errorCount === len) {
                            reject(new AggregateError(errors, 'All promises were rejected'))
                        }
                    })
            }
        }
    })
}
```


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://anjia1.gitbook.io/web/js/runtime/promise/implement.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
