Open yuanyuanbyte opened 2 years ago
本系列的主题是 JavaScript 深入系列,每期讲解一个技术要点。如果你还不了解各系列内容,文末点击查看全部文章,点我跳转到文末。
如果觉得本系列不错,欢迎 Star,你的支持是我创作分享的最大动力。
我们在上篇用了很大功夫实现了 Promise 的核心方法,并且通过了 Promises/A+ 官方872个测试用例测试,接下来实现这些静态方法已经是小菜一碟了,因为这些 API 全部是对前面的封装而已。
上篇文章在这里:JavaScript 深入系列之 Promise 核心原理的模拟实现,通过 Promises/A+ 官方872个测试用例
官方 Promise 还有很多API ,除了 then 方法以外还有 两个实例方法:
◾ 以及目前 Promise 规范的 六个静态方法:
虽然这些都不在 Promise/A+ 规范里面,但是我们也来实现一下吧,加深理解。其实我们前面我们用了很大功夫实现了 Promise/A+ ,现在再来实现这些已经是小菜一碟了,因为这些API全部是前面的封装而已。
Promise.resolve(value) 将给定的一个值转为Promise对象。
"then"
resolve()
根据规范我们这样实现(写法一):
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;
Promise.reject()方法返回一个带有拒绝原因的Promise对象。
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
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;
catch() 方法返回一个Promise,并且处理拒绝的情况。它的行为与调用Promise.prototype.then(undefined, onRejected) 相同。
catch()
Promise.prototype.then(undefined, onRejected)
事实上, calling obj.catch(onRejected) 内部calls obj.then(undefined, onRejected)。(这句话的意思是,我们显式使用obj.catch(onRejected),内部实际调用的是obj.then(undefined, onRejected))
obj.catch(onRejected)
obj.then(undefined, onRejected)
Promise.prototype.catch()方法是.then(null, rejection)或.then(undefined, rejection)的别名,用于指定发生错误时的回调函数。
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
测试通过,没毛病😏
finally() 方法返回一个Promise。在promise结束时,无论结果是fulfilled或者是rejected,都会执行指定的回调函数。这为在Promise是否成功完成后都需要执行的代码提供了一种方式。
finally()
这避免了同样的语句需要在then()和catch()中各写一次的情况。该方法是 ES2018 引入标准的。
then()
由于无法知道promise的最终状态,所以finally的回调函数中不接收任何参数,它仅用于无论最终结果如何都要执行的情况。
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
测试通过 👏👏👏
Promise.all() 方法接收一个promise的iterable类型(注:Array,Map,Set都属于ES6的iterable类型)的输入,并且只返回一个Promise实例, 输入的所有promise的resolve回调的结果是一个数组。
Promise.all()
promise
iterable
resolve
返回的这个Promise的resolve回调执行是在所有输入的promise的resolve回调都结束,或者输入的iterable里没有promise了的时候。它的reject回调执行是,只要任何一个输入的promise的reject回调执行或者输入不合法的promise就会立即抛出错误,并且reject的是第一个抛出的错误信息。
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"
2021年12月30日 更新
Byron_Yan 同学 在2021年12月30日留言:
promise.all实现,我发现入参数组元素是含有 then 属性的对象或者函数,不会原样返回;我觉得不用判断是否是 promise 的实例了,myPromise.resolve 的处理就可以了
我按照所给的用例跑了一下,测试代码如下:
const myPromsie = require('../myPromiseFully') const p1 = Promise.resolve(3); const p2 = { then: function (onFulfill) { onFulfill('then函数') } } const p3 = 42; Promise.all([p1, p2, p3]).then(result => { console.log('原生 all fulfilled :>> ', result); }, reason => { console.log('原生 all rejected :>> ', reason) }) myPromsie.all([p1, p2, p3]).then(result => { console.log('手写 all fulfilled :>> ', result); }, reason => { console.log('手写 all rejected :>> ', reason) })
经测试确实存在上述问题,我们修改一下逻辑:
class myPromise { ... /** * 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与thenable对象 + if (item instanceof myPromise || (value instanceof Object && 'then' in value)) { 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;
再执行一下前面的测试用例:
测试通过✌
下面我们对上面代码做一下优化
对thenable对象的处理其实借鉴了myPromise.resolve()的实现:
myPromise.resolve()
/** * 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.all方法的又恰恰用到了myPromise.resolve(),所以这样的写法其实很多余:
if (item instanceof myPromise || (value instanceof Object && 'then' in value)) { ... } else { // 参数里中非Promise值,原样返回在数组里 count++; result[index] = item; count === promises.length && resolve(result); }
这里直接用myPromsie.resolve才是最优解(至少现在我认为是)
myPromsie.resolve
Promsie.all 最终版的实现如下:
Promsie.all
/** * 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) => { // myPromise.resolve方法中已经判断了参数是否为promise与thenable对象,所以无需在该方法中再次判断 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 { return reject(new TypeError('Argument is not iterable')) } }) }
该写法通过了测试👏👏👏:
感谢Byron_Yan 同学的留言 👍👍👍
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() 方法内部会怎么处理:
Promise.allSettled(iterable)
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 ✌✌✌
本质上,这个方法和Promise.all()是相反的。
Promise.any() 接收一个Promise可迭代对象,只要其中的一个 promise 成功,就返回那个已经成功的 promise 。
Promise.any()
如果可迭代对象中没有一个 promise 成功(即所有的 promises 都失败/拒绝),就返回一个失败的 promise 和AggregateError类型的实例,它是 Error 的一个子类,用于把单一的错误集合在一起。
AggregateError
(即将非Promise值,转换为Promise并当做成功)
(如果所有Promise都失败,则报错)
注意! Promise.any() 方法依然是实验性的,尚未被所有的浏览器完全支持。它当前处于 TC39 第四阶段草案(Stage 4)
在 node v14.15.4 版本下测试 Promise.any() 发现还没有被支持:
node v14.15.4
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
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 很快完成
测试通过 😊
Promise.race(iterable) 方法返回一个 promise,一旦迭代器中的某个promise解决或拒绝,返回的 promise就会解决或拒绝。
Promise.race(iterable)
一个待定的 Promise 只要给定的迭代中的一个promise解决或拒绝,就采用第一个promise的值作为它的返回值,从而异步地解析或拒绝(一旦堆栈为空)。
race 函数返回一个 Promise,它将与第一个传递的 promise 相同的完成方式被完成。它可以是完成( resolves),也可以是失败(rejects),这要取决于第一个完成的方式是两个中的哪个。
race
如果传的迭代是空的,则返回的 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上看:
或者
/** * 在 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;
更多更全更详细 的 优质内容, 猛戳这里查看
查看原文
查看全部文章
各系列文章汇总:https://github.com/yuanyuanbyte/Blog
我是圆圆,一名深耕于前端开发的攻城狮。
本系列的主题是 JavaScript 深入系列,每期讲解一个技术要点。如果你还不了解各系列内容,文末点击查看全部文章,点我跳转到文末。
如果觉得本系列不错,欢迎 Star,你的支持是我创作分享的最大动力。
前言
我们在上篇用了很大功夫实现了 Promise 的核心方法,并且通过了 Promises/A+ 官方872个测试用例测试,接下来实现这些静态方法已经是小菜一碟了,因为这些 API 全部是对前面的封装而已。
上篇文章在这里:JavaScript 深入系列之 Promise 核心原理的模拟实现,通过 Promises/A+ 官方872个测试用例
官方 Promise 还有很多API ,除了 then 方法以外还有 两个实例方法:
◾ 以及目前 Promise 规范的 六个静态方法:
虽然这些都不在 Promise/A+ 规范里面,但是我们也来实现一下吧,加深理解。其实我们前面我们用了很大功夫实现了 Promise/A+ ,现在再来实现这些已经是小菜一碟了,因为这些API全部是前面的封装而已。
1. 实现 Promise.resolve
Promise.resolve(value) 将给定的一个值转为Promise对象。
"then"
方法),返回的promise会“跟随”这个thenable的对象,采用它的最终状态;resolve()
方法 (状态为fulfilled)。根据规范我们这样实现(写法一):
使用官方例子测试一下:
输出结果:
测试通过 ✌
静态方法改造
类(class)通过 static 关键字定义静态方法。不能在类的实例上调用静态方法,而应该通过类本身调用。这些通常是实用程序方法,例如创建或克隆对象的功能。
类相当于实例的原型,所有在类中定义的方法,都会被实例继承。如果在一个方法前,加上static关键字,就表示该方法不会被实例继承,而是直接通过类来调用,这就称为“静态方法”。
写法二、使用静态方法 static:
2. 实现 Promise.reject
Promise.reject()
方法返回一个带有拒绝原因的Promise
对象。官方例子:
输出结果:
根据规范我们这样实现(写法一):
使用官方用例测试一下:
输出结果:
测试通过 ✌
写法二、使用静态方法 static:
3. 实现 Promise.prototype.catch
catch()
方法返回一个Promise
,并且处理拒绝的情况。它的行为与调用Promise.prototype.then(undefined, onRejected)
相同。事实上, calling
obj.catch(onRejected)
内部callsobj.then(undefined, onRejected)
。(这句话的意思是,我们显式使用obj.catch(onRejected)
,内部实际调用的是obj.then(undefined, onRejected)
)Promise.prototype.catch()
方法是.then(null, rejection)
或.then(undefined, rejection)
的别名,用于指定发生错误时的回调函数。因此我们可以这样来实现:
就一行代码,我的天,居然这么简单😱
我们用官方例子来测试一下吧
输出:
测试通过,没毛病😏
4. 实现 Promise.prototype.finally
finally()
方法返回一个Promise
。在promise结束时,无论结果是fulfilled或者是rejected,都会执行指定的回调函数。这为在Promise
是否成功完成后都需要执行的代码提供了一种方式。这避免了同样的语句需要在
then()
和catch()
中各写一次的情况。该方法是 ES2018 引入标准的。由于无法知道promise的最终状态,所以
finally
的回调函数中不接收任何参数,它仅用于无论最终结果如何都要执行的情况。根据规范我们这样实现:
对,就这么简单 ✌
测试一下:
输出结果:
测试通过 👏👏👏
5. 实现 Promise.all
Promise.all()
方法接收一个promise
的iterable
类型(注:Array,Map,Set都属于ES6的iterable类型)的输入,并且只返回一个Promise
实例, 输入的所有promise
的resolve
回调的结果是一个数组。Promise.all
等待所有都完成(或第一个失败)(也就是如果参数里的某值不是Promise,则需要原样返回在数组里)
根据规范我们这样实现:
使用官方例子测试一下:
输出结果:
测试通过 👏👏👏
测试 Promise.all 的快速返回失败行为
Promise.all 在任意一个传入的 promise 失败时返回失败。例如,如果你传入的 promise中,有四个 promise 在一定的时间之后调用成功函数,有一个立即调用失败函数,那么 Promise.all 将立即变为失败。
输出结果:
测试通过 👏👏👏
2021年12月30日 更新
Byron_Yan 同学 在2021年12月30日留言:
我按照所给的用例跑了一下,测试代码如下:
经测试确实存在上述问题,我们修改一下逻辑:
再执行一下前面的测试用例:
测试通过✌
下面我们对上面代码做一下优化
对thenable对象的处理其实借鉴了
myPromise.resolve()
的实现:而我们
Promise.all
方法的又恰恰用到了myPromise.resolve()
,所以这样的写法其实很多余:这里直接用
myPromsie.resolve
才是最优解(至少现在我认为是)Promsie.all
最终版的实现如下:该写法通过了测试👏👏👏:
感谢Byron_Yan 同学的留言 👍👍👍
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() 方法内部会怎么处理:输出结果:
我们发现 Promise.allSettled() 方法内部将非 Promise 值转换成 Promise 了
那下面我们就将非 Promise 值通过 Promise.resolve 转换为 Promise 进行统一处理
根据规范我们这样实现:
使用官方例子测试一下:
输出结果:
测试通过 perfect ✌✌✌
7. 实现 Promise.any
本质上,这个方法和Promise.all()是相反的。
Promise.any()
接收一个Promise可迭代对象,只要其中的一个 promise 成功,就返回那个已经成功的 promise 。如果可迭代对象中没有一个 promise 成功(即所有的 promises 都失败/拒绝),就返回一个失败的 promise 和
AggregateError
类型的实例,它是 Error 的一个子类,用于把单一的错误集合在一起。(即将非Promise值,转换为Promise并当做成功)
(如果所有Promise都失败,则报错)
在
node v14.15.4
版本下测试Promise.any()
发现还没有被支持:Uncaught TypeError: Promise.any is not a function
既然是处于草案阶段的实验性 API ,如果想要在各版本浏览器中兼容性使用,那实现
Promise.any()
就很有必要 💪根据规范我们这样实现:
使用官方例子测试一下:
发现报错了,提示 AggregateErro 没有定义,这里不是我们代码的问题,是因为这个 AggregateErro ,
node v14.15.4
版本没有支持,按理说这个版本已经很高了,为什么还没有支持呢?和 Promise.any() 一样,这个 AggregateError 也是一个实验中的功能,处于Stage 3 Draft (第三阶段草案):
我们通过升级 node 版本来兼容这些处于草案阶段的实验功能~
从
node v14.15.4
升级到最新的node v16.13.0
再次验证即可通过:
用其他用例测试一下该方法:
输出结果:
测试通过 😊
8. 实现 Promise.race()
Promise.race(iterable)
方法返回一个 promise,一旦迭代器中的某个promise解决或拒绝,返回的 promise就会解决或拒绝。一个待定的 Promise 只要给定的迭代中的一个promise解决或拒绝,就采用第一个promise的值作为它的返回值,从而异步地解析或拒绝(一旦堆栈为空)。
race
函数返回一个Promise
,它将与第一个传递的 promise 相同的完成方式被完成。它可以是完成( resolves),也可以是失败(rejects),这要取决于第一个完成的方式是两个中的哪个。如果传的迭代是空的,则返回的 promise 将永远等待。
如果迭代包含一个或多个非承诺值和/或已解决/拒绝的承诺,则 Promise.race 将解析为迭代中找到的第一个值。
根据规范我们这样实现:
最后测试一下代码:
输出结果为:
测试通过 🎉🎉🎉
完整代码
手写 Promise 全部方法的完整版代码较长,这里如果看不清楚的可以去我的GitHub上看:
或者
❤️ 结尾
更多更全更详细 的 优质内容, 猛戳这里查看
参考
查看原文
查看全部文章
博文系列目录
交流
各系列文章汇总:https://github.com/yuanyuanbyte/Blog
我是圆圆,一名深耕于前端开发的攻城狮。