深入学习 JavaScript Promise

概念

Promise 是一个 ECMAScript 6 提供的类,目的是更加优雅地书写复杂的异步任务,解决js中多个异步回调难以维护和控制的问题。

什么时候适合用 Promise 而不是传统回调函数?当需要多次顺序执行异步操作的时候,例如,如果想通过异步方法先后检测用户名和密码,需要先异步检测用户名,然后再异步检测密码的情况下就很适合 Promise。

如何使用 Promise

静态方法

方法 说明
Promise.resolve(param) 等同于 new Promise(function (resolve.,reject){resolve(param)})
Promise.reject(reason) 等同于 new Promise(function (resolve.,reject){reject(reason)})
Promise.all([p1,…,pn]) 输入一组promise返回一个新的promise,全部promise都是fulfilled结果才是fulfilled状态;如果有一个失败,结果promise就是失败
Promise.allSettled([p1,…,pn]) 输入一组promise返回一个新的promise,所有的promise状态改变后,结果promise变成fulfilled
Promise.race([p1,…,pn]) 输入一组promise返回一个新的promise,结果promise的状态跟随第一个变化的promsie状态,最先返回promise是成功的,结果promise就是成功,否则就是失败

实例方法

方法 说明
promise.then(onFulfilled,onRejected) promise状态改变之后的回调,返回新的promise对想
Promise.catch(reason) 同promise.then(null,onRejected),promise状态为rejected回调
Promise.finally( function(reason){} ) 不管promise的状态如何都会执行

Promise 的应用与分析

信号灯

3秒后亮一次红灯,再过2秒亮一次绿灯,再过1秒亮一次黄灯,如此循环一直交替下去。

const LIGHTS = [
    {
        color: 'red',
        time: 3000
    },
    {
        color: 'green',
        time: 2000
    },
    {
        color: 'yellow',
        time: 1000
    }
];
function light({color, time}) {
    return new Promise((resolve, reject) => {
        try {
            setTimeout(() => {
                console.log(new Date().getSeconds(), color);
                resolve();
            }, time);
        } catch(e) {
            reject(e);
        }
    });
}
function main() {
    let p = Promise.resolve();
    LIGHTS.forEach(x => {
        p = p.then(() => light(x));
    });
    p.then(() => main());
}
main();
复制代码

Promise.all 并发限制

要求每个时刻并发执行的promise数量是固定的,最终的执行结果还是保持与原来的Promise.all一致。

function multiRequest(urls = [], maxNum) {
  // 请求总数量
  const len = urls.length;
  // 根据请求数量创建一个数组来保存请求的结果
  const result = new Array(len).fill(false);
  // 当前完成的数量
  let count = 0;

  return new Promise((resolve, reject) => {
    // 请求maxNum个
    while (count < maxNum) {
      next();
    }
    function next() {
      let current = count++;
      // 处理边界条件
      if (current >= len) {
        // 请求全部完成就将promise置为成功状态, 然后将result作为promise值返回
        !result.includes(false) && resolve(result);
        return;
      }
      const url = urls[current];
      console.log(`开始 ${current}`, new Date().toLocaleString());
      fetch(url)
        .then((res) => {
          // 保存请求结果
          result[current] = res;
          console.log(`完成 ${current}`, new Date().toLocaleString());
          // 请求没有全部完成, 就递归
          if (current < len) {
            next();
          }
        })
        .catch((err) => {
          console.log(`结束 ${current}`, new Date().toLocaleString());
          result[current] = err;
          // 请求没有全部完成, 就递归
          if (current < len) {
            next();
          }
        });
    }
  });
}
复制代码

Promise 执行顺序

/* Promise构造函数的参数是一个执行器,是同步的;
   构造完立即注册then函数,等同步代码执行完毕后,执行then函数。*/
new Promise((resolve, reject) => {
    console.log("1"); // 1. Promise构造函数接受的参数是一个需要立即执行的函数, 是一个同步任务
    resolve();
  })
.then(() => { // 2. 注册then方法,把它加到微任务队列
    // 3. 没有同步代码,开始执行该微任务
    console.log("2");
    new Promise((resolve, reject) => { // 4. 继续执行Promise构造函数
        console.log("3");
        resolve();
    })
    .then(() => { // 5. 注册其then方法,将其加到微任务队列
        console.log("4"); // 7. 执行
    })
    .then(() => { // 8. 注册
        console.log("5"); // 10. 执行
    });
})
.then(() => { // 6. 没有同步代码,第一个then执行完毕,继续注册外层 Promise 的第二个 then 
    console.log("6"); // 9. 执行
});
// 输出: 1 2 3 4 6 5
复制代码
new Promise((resolve, reject) => {
    console.log("1"); // 1. 构造函数的参数,先执行
    resolve();
  })
.then(() => { // 2. 注册第一个then
    console.log("2"); // 3. 执行第一个 then
    // 看到return ,需要将表达式执行完毕,才能执行外层第二个then
    return new Promise((resolve, reject) => {
        console.log("3"); // 4. 构造函数执行
        resolve();
    })
    .then(() => { // 5. 注册
        console.log("4"); // 6. 执行
    })
    .then(() => { // 7. 注册
        console.log("5"); // 8. 执行
    });
})
.then(() => { // 9. 注册
    console.log("6"); // 10. 执行
});
// 输出: 1 2 3 4 6 5
复制代码

手写一个 Promise

Promise A+ 规范

我们想要手写一个 Promise,就要遵循 Promise/A+ 规范。
Promise/A+ 规范详细阐述了如何实现一个符合标准的 Promise 类库。

  1. Promise 状态

Promise 的三个状态分别是 pending、fulfilled 和 rejected。

- pending: 待定,Promise 的初始状态。在此状态下可以落定 (settled) 为 fulfilled 或 rejected 状态。
- fulfilled: 兑现(解决),表示执行成功。Promise 被 resolve 后的状态,状态不可再改变,且有一个私有的值 value。
- rejected: 拒绝,表示执行失败。Promise 被 reject 后的状态,状态不可再改变,且有一个私有的原因 reason。
复制代码

注意:value 和 reason 也是不可变的,它们包含原始值或对象的不可修改的引用,默认值为 undefined。

  1. 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 多次调用。
复制代码
  1. 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。
复制代码

完整代码

Follow 大佬文章,只能说理解和半记忆了代码,有待进一步实践和推敲。。。

好在自己梳理一遍后,promise 内部的原理更加清楚了,实现细节见文末参考文献1和2。

const FULFILLED = 'fulfilled';
const PENDING = 'pending';
const REJECTED = 'rejected';
class MyPromise {
    constructor(excutor) {
        // executor接受两个参数:resolve和reject方法,让用户控制promise是成功还是失败;调用resolve是成功,调用reject是失败。
        try {
            excutor(this.resolve, this.reject); // 新建 promise 对象时,执行函数立即执行,同步的
        } catch (e) {
            this.reject(e);
        }
    }
    status = PENDING; // 异步任务的状态
    result = undefined; // 成功的结果
    reason = undefined; // 失败的原因
    onFulfilledCallBackArr = [];
    onRejectedCallBackArr = [];

    // 代码中调用resolve或reject时,在全局下调用的,此时resolve/reject里面this是window,用箭头函数让this指向当前实例
    // resolve 方法将状态改为 FULFILLED
    resolve = (result) => {
        if (this.status !== PENDING) return; // 状态只可以改变一次
        this.status = FULFILLED;
        this.result = result;
        while (this.onFulfilledCallBackArr.length) {
            this.onFulfilledCallBackArr.shift()(result);
        }
    }
    // reject 方法将状态改为 REJECTED
    reject = (reason) => {
        if (this.status !== PENDING) return; // 状态只可以改变一次
        this.status = REJECTED;
        this.reason = reason;
        while (this.onRejectedCallBackArr.length) {
            this.onRejectedCallBackArr.shift()(reason);
        }
    }
    // then 方法是核心:
    // 1. 存储回调函数,支持多次调用then,支持异步任务完成后按注册顺序依次执行成功/失败的回调函数
    // 2. 返回Promise,支持链式调用
    // 3. 参数支持undefined
    then(onFulfilled, onRejected) {
        onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
        onRejected = typeof onRejected === 'function' ? onRejected : reason => {
            throw reason
        };

        // then 的链式调用,需要返回一个新的 promise
        const newPromise = new MyPromise((resolve, reject) => {
            const fulfilledMicrotask = () => {
                // 创建一个微任务等待 promise2 完成初始化
                queueMicrotask(() => {
                    try {
                        // 获取成功回调函数的执行结果
                        const x = realOnFulfilled(this.result);
                        // 传入 resolvePromise 集中处理
                        resolvePromise(newPromise, x, resolve, reject);
                    } catch (error) {
                        reject(error)
                    }
                })
            };

            const rejectedMicrotask = () => {
                // 创建一个微任务等待 promise2 完成初始化
                queueMicrotask(() => {
                    try {
                        // 调用失败回调,并且把原因返回
                        const x = realOnRejected(this.reason);
                        // 传入 resolvePromise 集中处理
                        resolvePromise(newPromise, x, resolve, reject);
                    } catch (error) {
                        reject(error)
                    }
                })
            };

            switch (this.status) {
                case PENDING: // 判断如果异步任务还没有完成,则保存成功和失败的回调,等任务完成后依次执行
                    this.onFulfilledCallBackArr.push(fulfilledMicrotask);
                    this.onRejectedCallBackArr.push(rejectedMicrotask);
                    break;
                case FULFILLED:
                    fulfilledMicrotask();
                    break;
                case REJECTED:
                    rejectedMicrotask();
                    break;
            }
        });
        return newPromise;
    }

    catch (onRejected) {
        this.then(undefined, onRejected);
    }

    static resolve(x) {
        // 如果传入 MyPromise 就直接返回
        if (parameter instanceof MyPromise) {
            return parameter;
        } else {
            return new MyPromise((resolve, reject) => {
                resolve(x)
            });
        }
    }

    static reject(x) {
        return new MyPromise((resolve, reject) => {
            reject(x);
        });
    }

    // 全部成功
    static all(promises) {
        return new MyPromise((resolve, reject) => {
            let values = [];
            let count = 0;
            promises.map((p, index) => {
                if (p instanceof MyPromise) {
                    p.then(v => {
                        values[index] = v;
                    });
                } else {
                    values[index] = p;
                }
                count++;
                if (count === promises.length) {
                    resolve(value);
                }
            })
        });
    }

    // 一个成功就成功
    static race() {
        return new MyPromise((resolve, reject) => {
            promises.map(item => {
                if (item instanceof Promise) {
                    item.then(
                        resolve, reject)
                } else {
                    resolve(item)
                }
            })
        });
    }

    // 内部方法
    resolvePromise(p, x, resolve, reject) {
        // 如果相等了,说明return的是自己,抛出类型错误并返回
        if (promise2 === x) {
            return reject(new TypeError('Chaining cycle detected for promise #<Promise>'));
        }
        if (x instanceof MyPromise) { // 判断x是不是 MyPromise 实例对象
            // 执行 x,调用 then 方法,目的是将其状态变为 fulfilled 或者 rejected
            // x.then(value => resolve(value), reason => reject(reason))
            // 简化之后
            x.then(resolve, reject);
        } else {
            // 普通值
            resolve(x)
        }
    }
}

module.exports = MyPromise;
复制代码

参考

  1. JavaScript进阶 异步编程
  2. 从一道让我失眠的 Promise 面试题开始,深入分析 Promise 实现细节
  3. 「一次写过瘾」手写Promise全家桶+Generator+async/await
© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享