观感度:????????????????????

口味:海底捞

烹饪工夫:15min

本文已收录在前端食堂同名仓库Github github.com/Geekhyt,欢迎光临食堂,如果感觉酒菜还算可口,赏个 Star 对食堂老板来说是莫大的激励。

手写 Promise 全家桶

Promise/A+ 标准镇楼!

如果你没读过 Promise/A+ 标准也没关系,我帮你总结了如下三局部重点:

不过倡议看完本文后还是要亲自去读一读,不多 bb,开始展现。

标准重点

1.Promise 状态

Promise 的三个状态别离是 pendingfulfilledrejected

  • pending: 待定,Promise 的初始状态。在此状态下能够落定 (settled)fulfilledrejected 状态。
  • fulfilled: 兑现(解决),示意执行胜利。Promise 被 resolve 后的状态,状态不可再扭转,且有一个公有的值 value。
  • rejected: 回绝,示意执行失败。Promise 被 reject 后的状态,状态不可再扭转,且有一个公有的起因 reason。

留神:value 和 reason 也是不可变的,它们蕴含原始值或对象的不可批改的援用,默认值为 undefined

2.Then 办法

要求必须提供一个 then 办法来拜访以后或最终的 value 或 reason。

promise.then(onFulfilled, onRejected)
  • 1.then 办法承受两个函数作为参数,且参数可选。
  • 2.如果可选参数不为函数时会被疏忽。
  • 3.两个函数都是异步执行,会放入事件队列期待下一轮 tick。
  • 4.当调用 onFulfilled 函数时,会将以后 Promise 的 value 值作为参数传入。
  • 5.当调用 onRejected 函数时,会将以后 Promise 的 reason 失败起因作为参数传入。
  • 6.then 函数的返回值为 Promise。
  • 7.then 能够被同一个 Promise 屡次调用。

3.Promise 解决过程

Promise 的解决过程是一个形象操作,接管一个 Promise 和一个值 x。

针对 x 的不同值解决以下几种状况:

  • 1.x 等于 Promise

抛出 TypeError 谬误,回绝 Promise。

  • 2.x 是 Promise 的实例

如果 x 处于待定状态,那么 Promise 持续期待直到 x 兑现或回绝,否则依据 x 的状态兑现/回绝 Promise。

  • 3.x 是对象或函数

取出 x.then 并调用,调用时将 this 指向 x。将 then 回调函数中失去的后果 y 传入新的 Promise 解决过程中,递归调用。

如果执行报错,则将以对应的失败起因回绝 Promise。

这种状况就是解决领有 then() 函数的对象或函数,咱们也叫它 thenable

  • 4.如果 x 不是对象或函数

以 x 作为值执行 Promise。

手写 Promise

1.首先定义 Promise 的三个状态

var PENDING = 'pending';var FULFILLED = 'fulfilled';var REJECTED = 'rejected';

2.咱们再来搞定 Promise 的构造函数

创立 Promise 时须要传入 execute 回调函数,接管两个参数,这两个参数别离用来兑现和回绝以后 Promise。

所以咱们须要定义 resolve()reject() 函数。

初始状态为 PENDING,在执行时可能会有返回值 value,在回绝时会有回绝起因 reason

同时须要留神,Promise 外部的异样不能间接抛出,须要进行异样捕捉。

function Promise(execute) {    var that = this;    that.state = PENDING;    function resolve(value) {        if (that.state === PENDING) {            that.state = FULFILLED;            that.value = value;        }    }    function reject(reason) {        if (that.state === PENDING) {            that.state = REJECTED;            that.reason = reason;        }    }    try {        execute(resolve, reject);    } catch (e) {        reject(e);    }}

3. 实现 then() 办法

then 办法用来注册以后 Promise 状态落定后的回调,每个 Promise 实例都须要有它,显然要写到 Promise 的原型 prototype 上,并且 then() 函数接管两个回调函数作为参数,别离是 onFulfilledonRejected

Promise.prototype.then = function(onFulfilled, onRejected) {}

依据下面第 2 条规定,如果可选参数不为函数时应该被疏忽,咱们要对参数进行如下判断。

onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : function(x) { return x; }onRejected = typeof onRejected === 'function' ? onRejected : function(e) { throw e; }

依据第 3 条规定,须要应用 setTimeout 提早执行,模仿异步。

依据第 4 条、第 5 条规定,须要依据 Promise 的状态来执行对应的回调函数。

在 PENDING 状态时,须要等到状态落定能力调用。咱们能够将 onFulfilled 和 onRejected 函数存到 Promise 的属性 onFulfilledFnonRejectedFn 中,

当状态扭转时别离调用它们。

var that = this;var promise;if (that.state === FULFILLED) {    setTimeout(function() {        onFulfilled(that.value);    });}if (that.state === REJECTED) {    setTimeout(function() {        onRejected(that.reason);    });}if (that.state === PENDING) {     that.onFulfilledFn = function() {        onFulfilled(that.value);    }    that.onRejectedFn = function() {        onRejected(that.reason);    }}

依据第 6 条规定,then 函数的返回值为 Promise,咱们别离给每个逻辑增加并返回一个 Promise。

同时,then 反对链式调用,咱们须要将 onFulfilledFn 和 onRejectedFn 改成数组。

var that = this;var promise;if (that.state === FULFILLED) {    promise = new Promise(function(resolve, reject) {        setTimeout(function() {            try {                onFulfilled(that.value);            } catch (reason) {                reject(reason);            }        });    });}if (that.state === REJECTED) {    promise = new Promise(function(resolve, reject) {        setTimeout(function() {            try {                onRejected(that.reason);            } catch (reason) {                reject(reason);            }        });    });}if (that.state === PENDING) {    promise = new Promise(function(resolve, reject) {        that.onFulfilledFn.push(function() {            try {                onFulfilled(that.value);            } catch (reason) {                reject(reason);            }        })        that.onRejectedFn.push(function() {            try {                onRejected(that.reason);            } catch (reason) {                reject(reason);            }        });    });}

与下面绝对应的,再将 Promise 的构造函数相应的进行革新。

  • 1.增加 onFulFilledFn 和 onRejectedFn 数组。
  • 2.resolve() 和 reject() 函数扭转状态时,须要异步调用数组中的函数,同样应用 setTimeout 来模仿异步。
function Promise(execute) {    var that = this;    that.state = PENDING;    that.onFulfilledFn = [];    that.onRejectedFn = [];    function resolve(value) {        setTimeout(function() {            if (that.value === PENDING) {                that.state = FULFILLED;                that.value = value;                that.onFulfilledFn.forEach(function(fn) {                    fn(that.value);                })            }        })    }    function reject(reason) {        setTimeout(function() {            if (that.value === PENDING) {                that.state = REJECTED;                that.reason = reason;                that.onRejectedFn.forEach(function(fn) {                    fn(that.reason);                })            }        })    }    try {        execute(resolve, reject);    } catch (e) {        reject(e);    }}

4.Promise 解决过程 resolvePromise()

Promise 解决过程分为以下几种状况,咱们须要别离进行解决:

1.x 等于 Promise TypeError 谬误

此时相当于 Promise.then 之后 return 了本人,因为 then 会期待 return 后的 Promise,导致本人期待本人,始终处于期待。。

function resolvePromise(promise, x) {    if (promise === x) {        return reject(new TypeError('x 不能等于 promise'));    }}

2.x 是 Promise 的实例

如果 x 处于待定状态,Promise 会持续期待直到 x 兑现或回绝,否则依据 x 的状态兑现/回绝 Promise。

咱们须要调用 Promise 在结构时的函数 resolve() 和 reject() 来扭转 Promise 的状态。

function resolvePromise(promise, x, resolve, reject) {    // ...    if (x instanceof Promise) {        if (x.state === FULFILLED) {            resolve(x.value);        } else if (x.state === REJECTED) {            reject(x.reason);        } else {            x.then(function(y) {                resolvePromise(promise, y, resolve, reject);            }, reject);        }    }}

3.x 是对象或函数

取出 x.then 并调用,调用时将 this 指向 x,将 then 回调函数中失去的后果 y 传入新的 Promise 解决过程中,递归调用。

如果执行报错,则将以对应的失败起因回绝 Promise。

x 可能是一个 thenable 而非真正的 Promise。

须要设置一个变量 executed 防止反复调用。

function resolvePromise(promise, x, resolve, reject) {    // ...    if ((x !== null) && ((typeof x === 'object' || (typeof x === 'function'))) {        var executed;        try {            var then = x.then;            if (typeof then === 'function') {                then.call(x, function(y) {                    if (executed) return;                    executed = true;                    return resolvePromise(promise, y, resolve, reject);                }, function (e) {                    if (executed) return;                    executed = true;                    reject(e);                })             } else {                resolve(x);            }        } catch (e) {            if (executed) return;            executed = true;            reject(e);        }    }}

4.间接将 x 作为值执行

function resolvePromise(promise, x, resolve, reject) {    // ...    resolve(x)}

测试

// 为了反对测试,将模块导出module.exports = {  deferred() {    var resolve;    var reject;    var promise = new Promise(function (res, rej) {      resolve = res;      reject = rej;    })    return {      promise,      resolve,      reject    }  }}

咱们能够选用这款测试工具对咱们写的 Promise 进行测试 Promise/A+ 测试工具: promises-aplus-tests。

目前反对 827 个测试用例,咱们只须要在导出模块的时候遵循 CommonJS 标准,依照要求导出对应的函数即可。

Promise.resolve

Promise.resolve() 能够实例化一个解决(fulfilled) 的 Promise。

Promise.resolve = function(value) {    if (value instanceof Promise) {        return value;    }    return new Promise(function(resolve, reject) {        resolve(value);    });}

Promise.reject

Promise.reject() 能够实例化一个 rejected 的 Promise 并抛出一个异步谬误(这个谬误不能通过try/catch捕捉,只能通过回绝处理程序捕捉)

Promise.reject = function(reason) {    return new Promise(function(resolve, reject) {        reject(reason);    });}

Promise.prototype.catch

Promise.prototype.catch() 办法用于给 Promise 增加回绝时的回调函数。

Promise.prototype.catch = function(onRejected) {    return this.then(null, onRejected);}

Promise.prototype.finally

Promise.prototype.finally() 办法用于给 Promise 增加一个不论最终状态如何都会执行的操作。

Promise.prototype.finally = function(fn) {    return this.then(function(value) {        return Promise.resolve(value).then(function() {            return value;        });    }, function(error) {        return Promise.resolve(reason).then(function() {            throw error;        });    });}

Promise.all

Promise.all() 办法会将多个 Promise 实例组合成一个新的 Promise 实例。

组合后的 Promise 实例只有当每个蕴含的 Promise 实例都解决(fulfilled)后才解决(fulfilled),如果有一个蕴含的 Promise 实例回绝(rejected)了,则合成的 Promise 也会回绝(rejected)。

两个留神点:

  • 传入的是可迭代对象,用 for...of 遍历 Iterable 更平安
  • 传入的每个实例不肯定是 Promise,须要用 Promise.resolve() 包装
Promise.all = function(promiseArr) {    return new Promise(function(resolve, reject) {        const length = promiseArr.length;        const result = [];        let count = 0;        if (length === 0) {            return resolve(result);        }        for (let item of promiseArr) {            Promise.resolve(item).then(function(data) {                result[count++] = data;                if (count === length) {                    resolve(result);                }            }, function(reason) {                reject(reason);            });        }    });}

Promise.race

Promise.race() 同样返回一个合成的 Promise 实例,其会返回这一组中最先解决(fulfilled)或回绝(rejected)的 Promise 实例的返回值。

Promise.race = function(promiseArr) {    return new Promise(function(resolve, reject) {        const length = promiseArr.length;        if (length === 0) {            return resolve();        }         for (let item of promiseArr) {            Promise.resolve(item).then(function(value) {                return resolve(value);            }, function(reason) {                return reject(reason);            });        }    });}

Promise.any

Promise.any() 相当于 Promise.all() 的反向操作,同样返回一个合成的 Promise 实例,只有其中蕴含的任何一个 Promise 实例解决(fulfilled)了,合成的 Promise 就解决(fulfilled)。

只有当每个蕴含的 Promise 都回绝(rejected)了,合成的 Promise 才回绝(rejected)。

Promise.any = function(promiseArr) {    return new Promise(function(resolve, reject) {        const length = promiseArr.length;        const result = [];        let count = 0;        if (length === 0) {            return resolve(result);        }         for (let item of promiseArr) {            Promise.resolve(item).then((value) => {                return resolve(value);            }, (reason) => {                result[count++] = reason;                if (count === length) {                    reject(result);                }            });        }    });}

Promise.allSettled

Promise.allSettled() 办法也是返回一个合成的 Promise,不过只有等到所有蕴含的每个 Promise 实例都返回后果落定时,不论是解决(fulfilled)还是回绝(rejected),合成的 Promise 才会完结。一旦完结,状态总是 fulfilled。

其返回的是一个对象数组,每个对象示意对应的 Promise 后果。

对于每个后果对象,都有一个 status 字符串。如果它的值为 fulfilled,则后果对象上存在一个 value 。如果值为 rejected,则存在一个 reason 。

Promise.allSettled = function(promiseArr) {  return new Promise(function(resolve) {    const length = promiseArr.length;    const result = [];    let count = 0;    if (length === 0) {      return resolve(result);    } else {      for (let item of promiseArr) {        Promise.resolve(item).then((value) => {            result[count++] = { status: 'fulfilled', value: value };            if (count === length) {                return resolve(result);            }        }, (reason) => {            result[count++] = { status: 'rejected', reason: reason };            if (count === length) {                return resolve(result);            }        });      }    }  });}// 应用 Promise.finally 实现Promise.allSettled = function(promises) {    // 也能够应用扩大运算符将 Iterator 转换成数组    // const promiseArr = [...promises]    const promiseArr = Array.from(promises)    return new Promise(resolve => {        const result = []        const len = promiseArr.length;        let count = len;        if (len === 0) {          return resolve(result);        }        for (let i = 0; i < len; i++) {            promiseArr[i].then((value) => {                result[i] = { status: 'fulfilled', value: value };            }, (reason) => {                result[i] = { status: 'rejected', reason: reason };            }).finally(() => {                 if (!--count) {                    resolve(result);                }            });        }    });}// 应用 Promise.all 实现Promise.allSettled = function(promises) {    // 也能够应用扩大运算符将 Iterator 转换成数组    // const promiseArr = [...promises]    const promiseArr = Array.from(promises)    return Promise.all(promiseArr.map(p => Promise.resolve(p).then(res => {      return { status: 'fulfilled', value: res }    }, error => {      return { status: 'rejected', reason: error }    })));};
  • 残缺代码仓库

手写 Generator 函数

先来简略回顾下 Generator 的应用:

function* webCanteenGenerator() {    yield '店小二儿,给我切两斤牛肉来';    yield '再来十八碗酒';    return '好酒!这酒有力量!';}var canteen = webCanteenGenerator();canteen.next();canteen.next();canteen.next();canteen.next();// {value: "店小二儿,给我切两斤牛肉来", done: false}// {value: "再来十八碗酒", done: false}// {value: "好酒!这酒有力量!", done: true}// {value: undefined, done: true}

// 简易版// 定义生成器函数,入参是任意汇合function webCanteenGenerator(list) {    var index = 0;    var len = list.length;    return {        // 定义 next 办法        // 记录每次遍历地位,实现闭包,借助自在变量做迭代过程中的“游标”        next: function() {            var done = index >= len; // 如果索引还没有超出汇合长度,done 为 false            var value = !done ? list[index++] : undefined; // 如果 done 为 false,则能够持续取值            // 返回遍历是否结束的状态和以后值            return {                done: done,                value: value            }        }    }}var canteen = webCanteenGenerator(['路线千万条', '平安第一条', '行车不标准']);canteen.next();canteen.next();canteen.next();// {done: false, value: "路线千万条"}// {done: false, value: "平安第一条"}// {done: false, value: "行车不标准"}// {done: true, value: undefined}
  • 残缺代码地址

手写 async/await

Generator 缺点:

  • 1.函数内部无奈捕捉异样
  • 2.多个 yield 会导致调试艰难

async 函数对 Generator 函数改良如下:

  • 1.内置执行器
  • 2.更好的语义
  • 3.更广的适用性
  • 4.返回值是 Promise

async/await 做的事件就是将 Generator 函数转换成 Promise,说白了,async 函数就是 Generator 函数的语法糖,await 命令就是外部 then 命令的语法糖。

const fetchData = (data) => new Promise((resolve) => setTimeout(resolve, 1000, data + 1))const fetchResult = async function () {    var result1 = await fetchData(1);    var result2 = await fetchData(result1);    var result3 = await fetchData(result2);    console.log(result3);}fetchResult();

能够尝试通过 Babel 官网转换一下上述代码,能够看到其外围就是 _asyncToGenerator 办法。

咱们上面来实现它。

function asyncToGenerator(generatorFn) {    // 将 Generator 函数包装成了一个新的匿名函数,调用这个匿名函数时返回一个 Promise    return function() {        // 生成迭代器,相当于执行 Generator 函数        // 如下面三碗不过岗例子中的 var canteen = webCanteenGenerator()        var gen = generatorFn.apply(this, arguments);        return new Promise(function(resolve, reject) {            // 利用 Generator 宰割代码片段,每一个 yield 用 Promise 包裹起来            // 递归调用 Generator 函数对应的迭代器,当迭代器执行实现时执行以后的 Promise,失败时则回绝 Promise            function step(key, arg) {                try {                    var info = gen[key](arg);                    var value = info.value;                } catch (error) {                    reject(error);                    return;                }                if (info.done) {                    // 递归终止条件,实现了就 resolve                    resolve(value);                } else {                    return Promise.resolve(value).then(function(value) {                        step('next', value);                    }, function(err) {                        step('throw', err);                    });                }            }            return step('next');        });    }}
  • 残缺代码地址

好了,本文到这里就告一段落,如果上述代码你发现有问题的中央,能够在评论区留言,一起探讨学习。

一些参考资源

  • 前端高手进阶(朱德龙)
  • JavaScript 设计模式外围原理与利用实际(修言)
  • ES6 系列之 Babel 将 Async 编译成了什么样子
  • 艰深通俗的了解Promise中的then
  • 手写async await的最简实现(20行)

❤️爱心三连击

1.看到这里了就点个赞反对下吧,你的是我创作的能源。

2.关注公众号前端食堂,你的前端食堂,记得按时吃饭

3.本文已收录在前端食堂 github.com/Geekhyt,求个小星星,感激Star。