看了就会,手写 Promise 全部 API 教程,包括处于 TC39 第四阶段草案的 Promise.any()

发表于 2年以前  | 总阅读数:423 次

前言

我们在 [上篇文章] 用了很大功夫实现了 Promise 的核心方法,并且通过了 Promises/A+ 官方872个测试用例测试,接下来实现这些静态方法已经是小菜一碟了,因为这些 API 全部是对前面的封装而已。

上篇文章在这里:[手把手一行一行代码教你 "手写 Promise",完美通过 Promises/A+ 官方872个测试用例]

官方 Promise 还有很多API ,除了 then 方法以外还有 两个实例方法:

  • Promise.prototype.catch
  • Promise.prototype.finally

◾ 以及目前 Promise 规范的 六个静态方法:

  • Promise.resolve
  • Promise.reject
  • Promise.all
  • Promise.allSettled
  • Promise.any
  • Promise.race

虽然这些都不在 Promise/A+ 规范里面,但是我们也来实现一下吧,加深理解。其实我们前面我们用了很大功夫实现了 Promise/A+ ,现在再来实现这些已经是小菜一碟了,因为这些API全部是前面的封装而已。

1. 实现 Promise.resolve

Promise.resolve(value) 将给定的一个值转为Promise对象。

  • 如果这个值是一个 promise ,那么将返回这个 promise ;
  • 如果这个值是thenable(即带有"then" 方法),返回的promise会“跟随”这个thenable的对象,采用它的最终状态;
  • 否则返回的promise将以此值完成,即以此值执行resolve()方法 (状态为fulfilled)。

根据规范我们这样实现(写法一):

class myPromise {
    ...
}

function resolvePromise(promise2, x, resolve, reject) {
    ...
}

/**
 * Promise.resolve()
 * @param {[type]} value 要解析为 Promise 对象的值 
 */
+ myPromise.resolve = function (value) {
+     // 如果这个值是一个 promise ,那么将返回这个 promise 
+     if (value instanceof myPromise) {
+         return value;
+     } else if (value instanceof Object && 'then' in value) {
+         // 如果这个值是thenable(即带有`"then" `方法),返回的promise会“跟随”这个thenable的对象,采用它的最终状态;
+         return new myPromise((resolve, reject) => {
+             value.then(resolve, reject);
+         })
+     }
+     
+     // 否则返回的promise将以此值完成,即以此值执行`resolve()`方法 (状态为fulfilled)
+     return new myPromise((resolve) => {
+         resolve(value)
+     })
+ }

module.exports = myPromise;

使用官方例子测试一下:

const myPromise = require('./promiseOtherAPI');

const promise1 = myPromise.resolve(123);

promise1.then((value) => {
  console.log(value);
  // expected output: 123
});

// Resolve一个thenable对象
var p1 = myPromise.resolve({
    then: function (onFulfill) {
        onFulfill("Resolving");
    }
});
console.log(p1 instanceof myPromise) // true, 这是一个Promise对象

setTimeout(() => {
    console.log('p1 :>> ', p1);
}, 1000);

p1.then(function (v) {
    console.log(v); // 输出"fulfilled!"
}, function (e) {
    // 不会被调用
});

// Thenable在callback之前抛出异常
// myPromise rejects
var thenable = {
    then: function (resolve) {
        throw new TypeError("Throwing");
        resolve("Resolving");
    }
};

var p2 = myPromise.resolve(thenable);
p2.then(function (v) {
    // 不会被调用
}, function (e) {
    console.log(e); // TypeError: Throwing
});

输出结果:

true
123
Resolving
TypeError: Throwing
p1 :>> myPromise {PromiseState: 'fulfilled', PromiseResult: 'Resolving', onFulfilledCallbacks: Array(1), onRejectedCallbacks: Array(1)}

测试通过 ✌

静态方法改造

类(class)通过 static 关键字定义静态方法。不能在类的实例上调用静态方法,而应该通过类本身调用。这些通常是实用程序方法,例如创建或克隆对象的功能。

类相当于实例的原型,所有在类中定义的方法,都会被实例继承。如果在一个方法前,加上static关键字,就表示该方法不会被实例继承,而是直接通过类来调用,这就称为“静态方法”。

写法二、使用静态方法 static:

class myPromise {
    ...

    resolve(result) {
        ...
    }

    reject(reason) {
        ...
    }

    then(onFulfilled, onRejected) {
        ...
    }

    /**
     * Promise.resolve()
     * @param {[type]} value 要解析为 Promise 对象的值 
     */
+   static resolve(value) {
+       // 如果这个值是一个 promise ,那么将返回这个 promise 
+       if (value instanceof myPromise) {
+           return value;
+       } else if (value instanceof Object && 'then' in value) {
+           // 如果这个值是thenable(即带有`"then" `方法),返回的promise会“跟随”这个thenable的对象,采用它的最终状态;
+           return new myPromise((resolve, reject) => {
+               value.then(resolve, reject);
+           })
+       }
+
+       // 否则返回的promise将以此值完成,即以此值执行`resolve()`方法 (状态为fulfilled)
+       return new myPromise((resolve) => {
+           resolve(value)
+       })
+   }
}

function resolvePromise(promise2, x, resolve, reject) {
    ...
}

module.exports = myPromise;

2. 实现 Promise.reject

Promise.reject()方法返回一个带有拒绝原因的Promise对象。

官方例子:

Promise.reject(new Error('fail')).then(function() {
  // not called
}, function(error) {
  console.error(error); // Stacktrace
});

输出结果:

根据规范我们这样实现(写法一):

class myPromise {
    ...
}

function resolvePromise(promise2, x, resolve, reject) {
    ...
}

myPromise.resolve = function (value) {
    ...
}

/**
 * myPromise.reject
 * @param {*} reason 表示Promise被拒绝的原因
 * @returns 
 */
+ myPromise.reject = function (reason) {
+     return new myPromise((resolve, reject) => {
+         reject(reason);
+     })
+ }

module.exports = myPromise;

使用官方用例测试一下:

const myPromise = require('./promiseOtherAPI')

myPromise.reject(new Error('fail')).then(function () {
    // not called
}, function (error) {
    console.error(error); // Error: fail
});

输出结果:

Error: fail

测试通过 ✌

写法二、使用静态方法 static:

class myPromise {
    ...

    resolve(result) {
        ...
    }

    reject(reason) {
        ...
    }

    then(onFulfilled, onRejected) {
        ...
    }

    /**
     * Promise.resolve()
     * @param {[type]} value 要解析为 Promise 对象的值 
     */
    static resolve(value) {
        // 如果这个值是一个 promise ,那么将返回这个 promise 
        if (value instanceof myPromise) {
            return value;
        } else if (value instanceof Object && 'then' in value) {
            // 如果这个值是thenable(即带有`"then" `方法),返回的promise会“跟随”这个thenable的对象,采用它的最终状态;
            return new myPromise((resolve, reject) => {
                value.then(resolve, reject);
            })
        }

        // 否则返回的promise将以此值完成,即以此值执行`resolve()`方法 (状态为fulfilled)
        return new myPromise((resolve) => {
            resolve(value)
        })
    }

    /**
     * myPromise.reject
     * @param {*} reason 表示Promise被拒绝的原因
     * @returns 
     */
+   static reject(reason) {
+       return new myPromise((resolve, reject) => {
+           reject(reason);
+       })
+   }
}

function resolvePromise(promise2, x, resolve, reject) {
    ...
}

module.exports = myPromise;

3. 实现 Promise.prototype.catch

catch() 方法返回一个Promise,并且处理拒绝的情况。它的行为与调用Promise.prototype.then(undefined, onRejected) 相同。

事实上, calling obj.catch(onRejected) 内部calls obj.then(undefined, onRejected)。(这句话的意思是,我们显式使用obj.catch(onRejected),内部实际调用的是obj.then(undefined, onRejected))

Promise.prototype.catch()方法是.then(null, rejection).then(undefined, rejection)的别名,用于指定发生错误时的回调函数。

因此我们可以这样来实现:

class myPromise {
    ...

    then(onFulfilled, onRejected) {
        ...
    }

+   catch (onRejected) {
+       return this.then(undefined, onRejected)
+   }
}

function resolvePromise(promise2, x, resolve, reject) {
    ...
}

module.exports = myPromise;

就一行代码,我的天,居然这么简单

我们用官方例子来测试一下吧

const myPromise = require('./promiseOtherAPI')

var p1 = new myPromise(function (resolve, reject) {
    resolve('Success');
});

p1.then(function (value) {
    console.log(value); // "Success!"
    throw 'oh, no!';
}).catch(function (e) {
    console.log(e); // "oh, no!"
}).then(function () {
    console.log('after a catch the chain is restored');
}, function () {
    console.log('Not fired due to the catch');
});

// 以下行为与上述相同
p1.then(function (value) {
    console.log(value); // "Success!"
    return Promise.reject('oh, no!');
}).catch(function (e) {
    console.log(e); // "oh, no!"
}).then(function () {
    console.log('after a catch the chain is restored');
}, function () {
    console.log('Not fired due to the catch');
});

// 捕获异常
const p2 = new myPromise(function (resolve, reject) {
    throw new Error('test');
});
p2.catch(function (error) {
    console.log(error);
});
// Error: test

输出:

Success
Success
Error: test
oh, no!
oh, no!
after a catch the chain is restored
after a catch the chain is restored

测试通过,没毛病

4. 实现 Promise.prototype.finally

finally() 方法返回一个Promise。在promise结束时,无论结果是fulfilled或者是rejected,都会执行指定的回调函数。这为在Promise是否成功完成后都需要执行的代码提供了一种方式。

这避免了同样的语句需要在then()catch()中各写一次的情况。该方法是 ES2018 引入标准的。

由于无法知道promise的最终状态,所以finally的回调函数中不接收任何参数,它仅用于无论最终结果如何都要执行的情况。

根据规范我们这样实现:

class myPromise {
    ...
    catch (onRejected) {
        return this.then(undefined, onRejected)
    }

    /**
     * finally
     * @param {*} callBack 无论结果是fulfilled或者是rejected,都会执行的回调函数
     * @returns 
     */
+   finally(callBack) {
+       return this.then(callBack, callBack)
+   }
}

function resolvePromise(promise2, x, resolve, reject) {
    ...
}

myPromise.resolve = function (value) {
    ...
}

myPromise.reject = function (reason) {
    ...
}

module.exports = myPromise;

对,就这么简单 ✌

测试一下:

const myPromise = require('./promiseOtherAPI')

let p1 = new Promise(function (resolve, reject) {
    resolve(1)
}).then(function (value) {
    console.log(value);
}).catch(function (e) {
    console.log(e);
}).finally(function () {
    console.log('finally');
});

输出结果:

1
finally

测试通过

5. 实现 Promise.all

Promise.all() 方法接收一个promiseiterable类型(注:Array,Map,Set都属于ES6的iterable类型)的输入,并且只返回一个Promise实例, 输入的所有promiseresolve回调的结果是一个数组。

返回的这个Promise的resolve回调执行是在所有输入的promise的resolve回调都结束,或者输入的iterable里没有promise了的时候。它的reject回调执行是,只要任何一个输入的promise的reject回调执行或者输入不合法的promise就会立即抛出错误,并且reject的是第一个抛出的错误信息。

  • Promise.all 等待所有都完成(或第一个失败)
  • 如果传入的参数是一个空的可迭代对象,则返回一个已完成(already resolved)状态的 Promise
  • 如果参数中包含非 promise 值,这些值将被忽略,但仍然会被放在返回数组中,如果 promise 完成的话 (也就是如果参数里的某值不是Promise,则需要原样返回在数组里)
  • 在任何情况下,Promise.all 返回的 promise 的完成状态的结果都是一个数组,它包含所有的传入迭代参数对象的值(也包括非 promise 值)。
  • 如果传入的 promise 中有一个失败(rejected),Promise.all 异步地将失败的那个结果给失败状态的回调函数,而不管其它 promise 是否完成

根据规范我们这样实现:

class myPromise {
    ...

    resolve(result) {
        ...
    }
    reject(reason) {
        ...
    }
    then(onFulfilled, onRejected) {
        ...
    }
    static resolve(value) {
        ...
    }
    static reject(reason) {
        ...
    }
    catch (onRejected) {
        ...
    }
    finally(callBack) {
        ...
    }

    /**
     * Promise.all
     * @param {iterable} promises 一个promise的iterable类型(注:Array,Map,Set都属于ES6的iterable类型)的输入
     * @returns 
     */
+   static all(promises) {
+       return new myPromise((resolve, reject) => {
+           // 参数校验
+           if (Array.isArray(promises)) {
+               let result = []; // 存储结果
+               let count = 0; // 计数器
+
+               // 如果传入的参数是一个空的可迭代对象,则返回一个已完成(already resolved)状态的 Promise
+               if (promises.length === 0) {
+                   return resolve(promises);
+               }
+
+               promises.forEach((item, index) => {
+                   //  判断参数是否为promise
+                   if (item instanceof myPromise) {
+                       myPromise.resolve(item).then(
+                           value => {
+                               count++;
+                               // 每个promise执行的结果存储在result中
+                               result[index] = value;
+                               // Promise.all 等待所有都完成(或第一个失败)
+                               count === promises.length && resolve(result);
+                           },
+                           reason => {
+                               /**
+                                * 如果传入的 promise 中有一个失败(rejected),
+                                * Promise.all 异步地将失败的那个结果给失败状态的回调函数,而不管其它 promise 是否完成
+                                */
+                               reject(reason);
+                           }
+                       )
+                   } else {
+                       // 参数里中非Promise值,原样返回在数组里
+                       count++;
+                       result[index] = item;
+                       count === promises.length && resolve(result);
+                   }
+               })
+           } else {
+               return reject(new TypeError('Argument is not iterable'))
+           }
+       })
+   }
}

function resolvePromise(promise2, x, resolve, reject) {
    ...
}

module.exports = myPromise;

使用官方例子测试一下:

const myPromise = require('../promiseOtherAPI');

const promise1 = myPromise.resolve(3);
const promise2 = 42;
const promise3 = new myPromise((resolve, reject) => {
    setTimeout(resolve, 100, 'foo');
});

myPromise.all([promise1, promise2, promise3]).then((values) => {
    console.log(values);
});
// expected output: Array [3, 42, "foo"]

输出结果:

(3) [3, 42, 'foo']

测试通过

测试 Promise.all 的快速返回失败行为

Promise.all 在任意一个传入的 promise 失败时返回失败。例如,如果你传入的 promise中,有四个 promise 在一定的时间之后调用成功函数,有一个立即调用失败函数,那么 Promise.all 将立即变为失败。

const myPromise = require('../promiseOtherAPI');

var p1 = new Promise((resolve, reject) => {
    setTimeout(resolve, 1000, 'one');
});
var p2 = new Promise((resolve, reject) => {
    setTimeout(resolve, 2000, 'two');
});
var p3 = new Promise((resolve, reject) => {
    setTimeout(resolve, 3000, 'three');
});
var p4 = new Promise((resolve, reject) => {
    setTimeout(resolve, 4000, 'four');
});
var p5 = new Promise((resolve, reject) => {
    reject('reject');
});

Promise.all([p1, p2, p3, p4, p5]).then(values => {
    console.log(values);
}, reason => {
    console.log(reason)
});

//From console:
//"reject"

输出结果:

测试通过

6. 实现 Promise.allSettled

Promise.allSettled(iterable)方法返回一个在所有给定的promise都已经fulfilled或rejected后的promise,并带有一个对象数组,每个对象表示对应的promise结果。

  • 当你有多个彼此不依赖的异步任务成功完成时,或者你总是想知道每个promise的结果时,通常使用它。
  • 相比之下,Promise.all() 更适合彼此相互依赖或者在其中任何一个reject时立即结束。

参数 iterable 是一个可迭代的对象,例如Array,其中每个成员都是Promise。

对于每个结果对象,都有一个 status 字符串。如果它的值为 fulfilled,则结果对象上存在一个 value 。如果值为 rejected,则存在一个 reason 。value(或 reason )反映了每个 promise 决议(或拒绝)的值。

在实现前我们需要验证一点,输入的非promise值应该怎么处理?

为此我们在 Promise.allSettled(iterable) 的参数 iterable 中传入一个非promise值,看一下 Promise.allSettled() 方法内部会怎么处理:

const promise1 = Promise.resolve(3);
const promise2 = 1;
const promises = [promise1, promise2];

Promise.allSettled(promises).
then((results) => results.forEach((result) => console.log(result)));

输出结果:

{status: 'fulfilled', value: 3}
{status: 'fulfilled', value: 1}

我们发现 Promise.allSettled() 方法内部将非 Promise 值转换成 Promise 了

那下面我们就将非 Promise 值通过 Promise.resolve 转换为 Promise 进行统一处理

根据规范我们这样实现:

class myPromise {
    ...

    resolve(result) {
        ...
    }

    reject(reason) {
        ...
    }

    then(onFulfilled, onRejected) {
        ...
    }

    static resolve(value) {
        ...
    }

    static reject(reason) {
        ...
    }

    catch (onRejected) {
        ...
    }

    finally(callBack) {
        ...
    }

    static all(promises) {
        ...
    }

    /**
     * Promise.allSettled 
     * @param {*} promises 一个promise的iterable类型(注:Array,Map,Set都属于ES6的iterable类型)的输入
     * @returns 
     */
+   static allSettled(promises) {
+       return new myPromise((resolve, reject) => {
+           // 参数校验
+           if (Array.isArray(promises)) {
+               let result = []; // 存储结果
+               let count = 0; // 计数器
+
+               // 如果传入的是一个空数组,那么就直接返回一个resolved的空数组promise对象
+               if (promises.length === 0) return resolve(promises);
+
+               promises.forEach((item, index) => {
+                   // 非promise值,通过Promise.resolve转换为promise进行统一处理
+                   myPromise.resolve(item).then(
+                       value => {
+                           count++;
+                           // 对于每个结果对象,都有一个 status 字符串。如果它的值为 fulfilled,则结果对象上存在一个 value 。
+                           result[index] = {
+                               status: 'fulfilled',
+                               value
+                           }
+                           // 所有给定的promise都已经fulfilled或rejected后,返回这个promise
+                           count === promises.length && resolve(result);
+                       },
+                       reason => {
+                           count++;
+                           /**
+                            * 对于每个结果对象,都有一个 status 字符串。如果值为 rejected,则存在一个 reason 。
+                            * value(或 reason )反映了每个 promise 决议(或拒绝)的值。
+                            */
+                           result[index] = {
+                               status: 'rejected',
+                               reason
+                           }
+                           // 所有给定的promise都已经fulfilled或rejected后,返回这个promise
+                           count === promises.length && resolve(result);
+                       }
+                   )
+               })
+           } else {
+               return reject(new TypeError('Argument is not iterable'))
+           }
+       })
+   }
}

function resolvePromise(promise2, x, resolve, reject) {
    ...
}

module.exports = myPromise;

使用官方例子测试一下:

const myPromise = require('../promiseOtherAPI');

const promise1 = myPromise.resolve(3);
const promise2 = 1;
const promises = [promise1, promise2];

myPromise.allSettled(promises).
then((results) => results.forEach((result) => console.log(result)));

setTimeout(() => {
    const p1 = myPromise.resolve(3);
    const p2 = new myPromise((resolve, reject) => setTimeout(reject, 100, 'foo'));
    const ps = [p1, p2];

    myPromise.allSettled(ps).
    then((results) => results.forEach((result) => console.log(result)));
}, 1000);

myPromise.allSettled([]).then((results) => console.log(results))

输出结果:

(0) []
{status: 'fulfilled', value: 3}
{status: 'fulfilled', value: 1}
{status: 'fulfilled', value: 3}
{status: 'rejected', reason: 'foo'}

测试通过 perfect ✌✌✌

7. 实现 Promise.any

本质上,这个方法和Promise.all()是相反的。

Promise.any() 接收一个Promise可迭代对象,只要其中的一个 promise 成功,就返回那个已经成功的 promise 。

如果可迭代对象中没有一个 promise 成功(即所有的 promises 都失败/拒绝),就返回一个失败的 promise 和AggregateError类型的实例,它是 Error 的一个子类,用于把单一的错误集合在一起。

  • 如果传入的参数是一个空的可迭代对象,则返回一个 已失败(already rejected) 状态的 Promise。
  • 如果传入的参数不包含任何 promise,则返回一个 异步完成 (asynchronously resolved)的 Promise。(即将非Promise值,转换为Promise并当做成功)
  • 只要传入的迭代对象中的任何一个 promise 变成成功(resolve)状态,或者其中的所有的 promises 都失败,那么返回的 promise 就会 异步地(当调用栈为空时) 变成成功/失败(resolved/reject)状态。(如果所有Promise都失败,则报错)

注意!Promise.any() 方法依然是实验性的,尚未被所有的浏览器完全支持。它当前处于 TC39 第四阶段草案(Stage 4)

node v14.15.4 版本下测试 Promise.any() 发现还没有被支持:

Uncaught TypeError: Promise.any is not a function

既然是处于草案阶段的实验性 API ,如果想要在各版本浏览器中兼容性使用,那实现 Promise.any() 就很有必要

根据规范我们这样实现:

class myPromise {
    ...

    resolve(result) {
        ...
    }

    reject(reason) {
        ...
    }

    then(onFulfilled, onRejected) {
        ...
    }

    static resolve(value) {
        ...
    }

    static reject(reason) {
        ...
    }

    catch (onRejected) {
        ...
    }

    finally(callBack) {
        ...
    }

    static all(promises) {
        ...
    }

    static allSettled(promises) {
        ...
    }


    /**
     * Promise.any()
     * @param {iterable} promises 一个promise的iterable类型(注:Array,Map,Set都属于ES6的iterable类型)的输入
     * @returns 
     */
+   static any(promises) {
+       return new myPromise((resolve, reject) => {
+           // 参数校验
+           if (Array.isArray(promises)) {
+               let errors = []; // 
+               let count = 0; // 计数器
+
+               // 如果传入的参数是一个空的可迭代对象,则返回一个 已失败(already rejected) 状态的 Promise。
+               if (promises.length === 0) return reject(new AggregateError('All promises were rejected'));
+
+               promises.forEach(item => {
+                   // 非Promise值,通过Promise.resolve转换为Promise
+                   myPromise.resolve(item).then(
+                       value => {
+                           // 只要其中的一个 promise 成功,就返回那个已经成功的 promise 
+                           resolve(value);
+                       },
+                       reason => {
+                           cout++;
+                           errors.push(reason);
+                           /**
+                            * 如果可迭代对象中没有一个 promise 成功,就返回一个失败的 promise 和AggregateError类型的实例,
+                            * AggregateError是 Error 的一个子类,用于把单一的错误集合在一起。
+                            */
+                           cout === promises.length && reject(new AggregateError(errors));
+                       }
+                   )
+               })
+           } else {
+               return reject(new TypeError('Argument is not iterable'))
+           }
+       })
+   }
}

function resolvePromise(promise2, x, resolve, reject) {
    ...
}

module.exports = myPromise;

使用官方例子测试一下:

发现报错了,提示 AggregateErro 没有定义,这里不是我们代码的问题,是因为这个 AggregateErro ,node v14.15.4 版本没有支持,按理说这个版本已经很高了,为什么还没有支持呢?

和 Promise.any() 一样,这个 AggregateError 也是一个实验中的功能,处于Stage 3 Draft (第三阶段草案):

我们通过升级 node 版本来兼容这些处于草案阶段的实验功能~

node v14.15.4 升级到最新的 node v16.13.0

再次验证即可通过:

用其他用例测试一下该方法:

const myPromise = require('../myPromiseFully');

/**
 * 验证Promise.any()方法
 */

// console.log(new AggregateError('All promises were rejected'));

myPromise.any([]).catch(e => {
    console.log(e);
});

const pErr = new Promise((resolve, reject) => {
    reject("总是失败");
});

const pSlow = new Promise((resolve, reject) => {
    setTimeout(resolve, 500, "最终完成");
});

const pFast = new Promise((resolve, reject) => {
    setTimeout(resolve, 100, "很快完成");
});

Promise.any([pErr, pSlow, pFast]).then((value) => {
    console.log(value);
    // 期望输出: "很快完成"
})


const pErr1 = new myPromise((resolve, reject) => {
    reject("总是失败");
});

const pErr2 = new myPromise((resolve, reject) => {
    reject("总是失败");
});

const pErr3 = new myPromise((resolve, reject) => {
    reject("总是失败");
});

myPromise.any([pErr1, pErr2, pErr3]).catch(e => {
    console.log(e);
})

输出结果:

AggregateError: All promises were rejected
AggregateError: All promises were rejected
很快完成

测试通过

8. 实现 Promise.race()

Promise.race(iterable) 方法返回一个 promise,一旦迭代器中的某个promise解决或拒绝,返回的 promise就会解决或拒绝。

一个待定的 Promise 只要给定的迭代中的一个promise解决或拒绝,就采用第一个promise的值作为它的返回值,从而异步地解析或拒绝(一旦堆栈为空)。

race 函数返回一个 Promise,它将与第一个传递的 promise 相同的完成方式被完成。它可以是完成( resolves),也可以是失败(rejects),这要取决于第一个完成的方式是两个中的哪个。

  • 如果传的迭代是空的,则返回的 promise 将永远等待。
  • 如果迭代包含一个或多个非承诺值和/或已解决/拒绝的承诺,则 Promise.race 将解析为迭代中找到的第一个值。

根据规范我们这样实现:

class myPromise {
    ...

    static resolve(value) {
        ...
    }

    static reject(reason) {
        ...
    }

    catch (onRejected) {
        ...
    }

    finally(callBack) {
        ...
    }

    static all(promises) {
        ...
    }

    static allSettled(promises) {
        ...
    }

    static any(promises) {
        ...
    }

    /**
     * Promise.race()
     * @param {iterable} promises 可迭代对象,类似Array。详见 iterable。
     * @returns 
     */
+   static race(promises) {
+       return new myPromise((resolve, reject) => {
+           // 参数校验
+           if (Array.isArray(promises)) {
+               // 如果传入的迭代promises是空的,则返回的 promise 将永远等待。
+               if (promises.length > 0) {
+                   promises.forEach(item => {
+                       /**
+                        * 如果迭代包含一个或多个非承诺值和/或已解决/拒绝的承诺,
+                        * 则 Promise.race 将解析为迭代中找到的第一个值。
+                        */
+                       myPromise.resolve(item).then(resolve, reject);
+                   })
+               }
+           } else {
+               return reject(new TypeError('Argument is not iterable'))
+           }
+       })
+   }
}

function resolvePromise(promise2, x, resolve, reject) {
    ...
}

module.exports = myPromise;

最后测试一下代码:

const myPromise = require('../myPromiseFully');

/**
 * 验证Promise.race()方法
 */

// 数组全是非Promise值,测试通过
let p1 = myPromise.race([1, 3, 4]);
setTimeout(() => {
    console.log('p1 :>> ', p1);
});

// 空数组,测试通过
let p2 = myPromise.race([]);
setTimeout(() => {
    console.log('p2 :>> ', p2);
});

const p11 = new myPromise((resolve, reject) => {
    setTimeout(resolve, 500, 'one');
});

const p22 = new myPromise((resolve, reject) => {
    setTimeout(resolve, 100, 'two');
});

// // 数组里有非Promise值,测试通过
myPromise.race([p11, p22, 10]).then((value) => {
    console.log('p3 :>> ', value);
    // Both resolve, but p22 is faster
});
// expected output: 10

// 数组里有'已解决的Promise' 和 非Promise值 测试通过
let p12 = myPromise.resolve('已解决的Promise')
setTimeout(() => {
    myPromise.race([p12, p22, 10]).then((value) => {
        console.log('p4 :>> ', value);
    });
    // expected output:已解决的Promise
});

// Promise.race的一般情况 测试通过
const p13 = new myPromise((resolve, reject) => {
    setTimeout(resolve, 500, 'one');
});

const p14 = new myPromise((resolve, reject) => {
    setTimeout(resolve, 100, 'two');
});

myPromise.race([p13, p14]).then((value) => {
    console.log('p5 :>> ', value);
    // Both resolve, but promise2 is faster
});
// expected output: "two"

输出结果为:

p1 :>>  myPromise {PromiseState: 'pending', PromiseResult: null, onFulfilledCallbacks: Array(0), onRejectedCallbacks: Array(0)}
p2 :>>  myPromise {PromiseState: 'pending', PromiseResult: null, onFulfilledCallbacks: Array(0), onRejectedCallbacks: Array(0)}
p3 :>>  10
p4 :>>  已解决的Promise
p5 :>>  two

测试通过

完整代码

手写 Promise 全部方法的完整版代码较长,这里如果看不清楚的可以去我的GitHub上看:

  • 手写 Promise 核心原理,完整的 Promise/A+ 实现,通过了 Promise/A+ 官方872个测试用例测试,根据规范实现了 ES6+ 的全部 API,包括处于 TC39 第四阶段草案的 Promise.any() https://github.com/yuanyuanbyte/Promise
/**
 * 在 myPromise.js 基础上,根据规范实现了 Promise 的全部方法:
 * - Promise.resolve()
 * - Promise.reject()
 * - Promise.prototype.catch()
 * - Promise.prototype.finally()
 * - Promise.all()
 * - Promise.allSettled()
 * - Promise.any()
 * - Promise.race()
 */
class myPromise {
    static PENDING = 'pending';
    static FULFILLED = 'fulfilled';
    static REJECTED = 'rejected';

    constructor(func) {
        this.PromiseState = myPromise.PENDING;
        this.PromiseResult = null;
        this.onFulfilledCallbacks = [];
        this.onRejectedCallbacks = [];
        try {
            func(this.resolve.bind(this), this.reject.bind(this));
        } catch (error) {
            this.reject(error)
        }
    }

    resolve(result) {
        if (this.PromiseState === myPromise.PENDING) {
            setTimeout(() => {
                this.PromiseState = myPromise.FULFILLED;
                this.PromiseResult = result;
                this.onFulfilledCallbacks.forEach(callback => {
                    callback(result)
                })
            });
        }
    }

    reject(reason) {
        if (this.PromiseState === myPromise.PENDING) {
            setTimeout(() => {
                this.PromiseState = myPromise.REJECTED;
                this.PromiseResult = reason;
                this.onRejectedCallbacks.forEach(callback => {
                    callback(reason)
                })
            });
        }
    }

    /**
     * [注册fulfilled状态/rejected状态对应的回调函数] 
     * @param {function} onFulfilled  fulfilled状态时 执行的函数
     * @param {function} onRejected  rejected状态时 执行的函数 
     * @returns {function} newPromsie  返回一个新的promise对象
     */
    then(onFulfilled, onRejected) {
        onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
        onRejected = typeof onRejected === 'function' ? onRejected : reason => {
            throw reason;
        };

        let promise2 = new myPromise((resolve, reject) => {
            if (this.PromiseState === myPromise.FULFILLED) {
                setTimeout(() => {
                    try {
                        let x = onFulfilled(this.PromiseResult);
                        resolvePromise(promise2, x, resolve, reject);
                    } catch (e) {
                        reject(e);
                    }
                });
            } else if (this.PromiseState === myPromise.REJECTED) {
                setTimeout(() => {
                    try {
                        let x = onRejected(this.PromiseResult);
                        resolvePromise(promise2, x, resolve, reject);
                    } catch (e) {
                        reject(e)
                    }
                });
            } else if (this.PromiseState === myPromise.PENDING) {
                this.onFulfilledCallbacks.push(() => {
                    setTimeout(() => {
                        try {
                            let x = onFulfilled(this.PromiseResult);
                            resolvePromise(promise2, x, resolve, reject)
                        } catch (e) {
                            reject(e);
                        }
                    });
                });
                this.onRejectedCallbacks.push(() => {
                    setTimeout(() => {
                        try {
                            let x = onRejected(this.PromiseResult);
                            resolvePromise(promise2, x, resolve, reject);
                        } catch (e) {
                            reject(e);
                        }
                    });
                });
            }
        })

        return promise2
    }

    /**
     * Promise.resolve()
     * @param {[type]} value 要解析为 Promise 对象的值 
     */
    static resolve(value) {
        // 如果这个值是一个 promise ,那么将返回这个 promise 
        if (value instanceof myPromise) {
            return value;
        } else if (value instanceof Object && 'then' in value) {
            // 如果这个值是thenable(即带有`"then" `方法),返回的promise会“跟随”这个thenable的对象,采用它的最终状态;
            return new myPromise((resolve, reject) => {
                value.then(resolve, reject);
            })
        }

        // 否则返回的promise将以此值完成,即以此值执行`resolve()`方法 (状态为fulfilled)
        return new myPromise((resolve) => {
            resolve(value)
        })
    }

    /**
     * Promise.reject()
     * @param {*} reason 表示Promise被拒绝的原因
     * @returns 
     */
    static reject(reason) {
        return new myPromise((resolve, reject) => {
            reject(reason);
        })
    }

    /**
     * Promise.prototype.catch()
     * @param {*} onRejected 
     * @returns 
     */
    catch (onRejected) {
        return this.then(undefined, onRejected)
    }

    /**
     * Promise.prototype.finally()
     * @param {*} callBack 无论结果是fulfilled或者是rejected,都会执行的回调函数
     * @returns 
     */
    finally(callBack) {
        return this.then(callBack, callBack)
    }

    /**
     * Promise.all()
     * @param {iterable} promises 一个promise的iterable类型(注:Array,Map,Set都属于ES6的iterable类型)的输入
     * @returns 
     */
    static all(promises) {
        return new myPromise((resolve, reject) => {
            // 参数校验
            if (Array.isArray(promises)) {
                let result = []; // 存储结果
                let count = 0; // 计数器

                // 如果传入的参数是一个空的可迭代对象,则返回一个已完成(already resolved)状态的 Promise
                if (promises.length === 0) {
                    return resolve(promises);
                }

                promises.forEach((item, index) => {
                    //  判断参数是否为promise
                    if (item instanceof myPromise) {
                        myPromise.resolve(item).then(
                            value => {
                                count++;
                                // 每个promise执行的结果存储在result中
                                result[index] = value;
                                // Promise.all 等待所有都完成(或第一个失败)
                                count === promises.length && resolve(result);
                            },
                            reason => {
                                /**
                                 * 如果传入的 promise 中有一个失败(rejected),
                                 * Promise.all 异步地将失败的那个结果给失败状态的回调函数,而不管其它 promise 是否完成
                                 */
                                reject(reason);
                            }
                        )
                    } else {
                        // 参数里中非Promise值,原样返回在数组里
                        count++;
                        result[index] = item;
                        count === promises.length && resolve(result);
                    }
                })
            } else {
                return reject(new TypeError('Argument is not iterable'))
            }
        })
    }

    /**
     * Promise.allSettled()
     * @param {iterable} promises 一个promise的iterable类型(注:Array,Map,Set都属于ES6的iterable类型)的输入
     * @returns 
     */
    static allSettled(promises) {
        return new myPromise((resolve, reject) => {
            // 参数校验
            if (Array.isArray(promises)) {
                let result = []; // 存储结果
                let count = 0; // 计数器

                // 如果传入的是一个空数组,那么就直接返回一个resolved的空数组promise对象
                if (promises.length === 0) return resolve(promises);

                promises.forEach((item, index) => {
                    // 非promise值,通过Promise.resolve转换为promise进行统一处理
                    myPromise.resolve(item).then(
                        value => {
                            count++;
                            // 对于每个结果对象,都有一个 status 字符串。如果它的值为 fulfilled,则结果对象上存在一个 value 。
                            result[index] = {
                                status: 'fulfilled',
                                value
                            }
                            // 所有给定的promise都已经fulfilled或rejected后,返回这个promise
                            count === promises.length && resolve(result);
                        },
                        reason => {
                            count++;
                            /**
                             * 对于每个结果对象,都有一个 status 字符串。如果值为 rejected,则存在一个 reason 。
                             * value(或 reason )反映了每个 promise 决议(或拒绝)的值。
                             */
                            result[index] = {
                                status: 'rejected',
                                reason
                            }
                            // 所有给定的promise都已经fulfilled或rejected后,返回这个promise
                            count === promises.length && resolve(result);
                        }
                    )
                })
            } else {
                return reject(new TypeError('Argument is not iterable'))
            }
        })
    }

    /**
     * Promise.any()
     * @param {iterable} promises 一个promise的iterable类型(注:Array,Map,Set都属于ES6的iterable类型)的输入
     * @returns 
     */
    static any(promises) {
        return new myPromise((resolve, reject) => {
            // 参数校验
            if (Array.isArray(promises)) {
                let errors = []; // 
                let count = 0; // 计数器

                // 如果传入的参数是一个空的可迭代对象,则返回一个 已失败(already rejected) 状态的 Promise。
                if (promises.length === 0) return reject(new AggregateError([], 'All promises were rejected'));

                promises.forEach(item => {
                    // 非Promise值,通过Promise.resolve转换为Promise
                    myPromise.resolve(item).then(
                        value => {
                            // 只要其中的一个 promise 成功,就返回那个已经成功的 promise 
                            resolve(value);
                        },
                        reason => {
                            count++;
                            errors.push(reason);
                            /**
                             * 如果可迭代对象中没有一个 promise 成功,就返回一个失败的 promise 和AggregateError类型的实例,
                             * AggregateError是 Error 的一个子类,用于把单一的错误集合在一起。
                             */
                            count === promises.length && reject(new AggregateError(errors, 'All promises were rejected'));
                        }
                    )
                })
            } else {
                return reject(new TypeError('Argument is not iterable'))
            }
        })
    }

    /**
     * Promise.race()
     * @param {iterable} promises 可迭代对象,类似Array。详见 iterable。
     * @returns 
     */
    static race(promises) {
        return new myPromise((resolve, reject) => {
            // 参数校验
            if (Array.isArray(promises)) {
                // 如果传入的迭代promises是空的,则返回的 promise 将永远等待。
                if (promises.length > 0) {
                    promises.forEach(item => {
                        /**
                         * 如果迭代包含一个或多个非承诺值和/或已解决/拒绝的承诺,
                         * 则 Promise.race 将解析为迭代中找到的第一个值。
                         */
                        myPromise.resolve(item).then(resolve, reject);
                    })
                }
            } else {
                return reject(new TypeError('Argument is not iterable'))
            }
        })
    }
}

/**
 * 对resolve()、reject() 进行改造增强 针对resolve()和reject()中不同值情况 进行处理
 * @param  {promise} promise2 promise1.then方法返回的新的promise对象
 * @param  {[type]} x         promise1中onFulfilled或onRejected的返回值
 * @param  {[type]} resolve   promise2的resolve方法
 * @param  {[type]} reject    promise2的reject方法
 */
function resolvePromise(promise2, x, resolve, reject) {
    if (x === promise2) {
        return reject(new TypeError('Chaining cycle detected for promise'));
    }

    if (x instanceof myPromise) {
        if (x.PromiseState === myPromise.PENDING) {
            x.then(y => {
                resolvePromise(promise2, y, resolve, reject)
            }, reject);
        } else if (x.PromiseState === myPromise.FULFILLED) {
            resolve(x.PromiseResult);
        } else if (x.PromiseState === myPromise.REJECTED) {
            reject(x.PromiseResult);
        }
    } else if (x !== null && ((typeof x === 'object' || (typeof x === 'function')))) {
        try {
            var then = x.then;
        } catch (e) {
            return reject(e);
        }

        if (typeof then === 'function') {
            let called = false;
            try {
                then.call(
                    x,
                    y => {
                        if (called) return;
                        called = true;
                        resolvePromise(promise2, y, resolve, reject);
                    },
                    r => {
                        if (called) return;
                        called = true;
                        reject(r);
                    }
                )
            } catch (e) {
                if (called) return;
                called = true;

                reject(e);
            }
        } else {
            resolve(x);
        }
    } else {
        return resolve(x);
    }
}

myPromise.deferred = function () {
    let result = {};
    result.promise = new myPromise((resolve, reject) => {
        result.resolve = resolve;
        result.reject = reject;
    });
    return result;
}

module.exports = myPromise;

本文由哈喽比特于2年以前收录,如有侵权请联系我们。
文章来源:https://mp.weixin.qq.com/s/kFNgEa60CPF4jA8QRXxoNA

 相关推荐

刘强东夫妇:“移民美国”传言被驳斥

京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。

发布于:1年以前  |  808次阅读  |  详细内容 »

博主曝三大运营商,将集体采购百万台华为Mate60系列

日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。

发布于:1年以前  |  770次阅读  |  详细内容 »

ASML CEO警告:出口管制不是可行做法,不要“逼迫中国大陆创新”

据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。

发布于:1年以前  |  756次阅读  |  详细内容 »

抖音中长视频App青桃更名抖音精选,字节再发力对抗B站

今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。

发布于:1年以前  |  648次阅读  |  详细内容 »

威马CDO:中国每百户家庭仅17户有车

日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。

发布于:1年以前  |  589次阅读  |  详细内容 »

研究发现维生素 C 等抗氧化剂会刺激癌症生长和转移

近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。

发布于:1年以前  |  449次阅读  |  详细内容 »

苹果据称正引入3D打印技术,用以生产智能手表的钢质底盘

据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。

发布于:1年以前  |  446次阅读  |  详细内容 »

千万级抖音网红秀才账号被封禁

9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...

发布于:1年以前  |  445次阅读  |  详细内容 »

亚马逊股东起诉公司和贝索斯,称其在购买卫星发射服务时忽视了 SpaceX

9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。

发布于:1年以前  |  444次阅读  |  详细内容 »

苹果上线AppsbyApple网站,以推广自家应用程序

据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。

发布于:1年以前  |  442次阅读  |  详细内容 »

特斯拉美国降价引发投资者不满:“这是短期麻醉剂”

特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。

发布于:1年以前  |  441次阅读  |  详细内容 »

光刻机巨头阿斯麦:拿到许可,继续对华出口

据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。

发布于:1年以前  |  437次阅读  |  详细内容 »

马斯克与库克首次隔空合作:为苹果提供卫星服务

近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。

发布于:1年以前  |  430次阅读  |  详细内容 »

𝕏(推特)调整隐私政策,可拿用户发布的信息训练 AI 模型

据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。

发布于:1年以前  |  428次阅读  |  详细内容 »

荣耀CEO谈华为手机回归:替老同事们高兴,对行业也是好事

9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。

发布于:1年以前  |  423次阅读  |  详细内容 »

AI操控无人机能力超越人类冠军

《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。

发布于:1年以前  |  423次阅读  |  详细内容 »

AI生成的蘑菇科普书存在可致命错误

近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。

发布于:1年以前  |  420次阅读  |  详细内容 »

社交媒体平台𝕏计划收集用户生物识别数据与工作教育经历

社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”

发布于:1年以前  |  411次阅读  |  详细内容 »

国产扫地机器人热销欧洲,国产割草机器人抢占欧洲草坪

2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。

发布于:1年以前  |  406次阅读  |  详细内容 »

罗永浩吐槽iPhone15和14不会有区别,除了序列号变了

罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。

发布于:1年以前  |  398次阅读  |  详细内容 »
 相关文章
Android插件化方案 5年以前  |  237231次阅读
vscode超好用的代码书签插件Bookmarks 2年以前  |  8065次阅读
 目录