Promise

Promise

Promise 是什么?

Promise 是异步编程的一种解决方案, 简单说就是一个容器,里面保存着一个尚未完成且预计在未来完成的异步操作

使用 new Promise 创建一个 Promise 对象, 用于表示一个异步操作的最终完成 (或失败)及其结果值, 有三种状态:
pending(进行中)
fulfilled(已完成)
rejected(已失败)

pending 状态可以通过 resolve异步操作转为 fulfilled 状态, 或者通过 reject 异步操作转为 rejected 状态

fulfilledrejected 为最终态, 状态一旦改变就不会再变

优点

  1. 摆脱了回调地狱, 可以进行 .then 的链式调用, 让异步操作变得更加同步化, 流程更加清晰规范, 提高了代码的可维护性和可读性

缺点

  1. 一旦执行无法取消
  2. 无法跟踪进度

Promise/A+ 规范解读

术语

  1. promise 是一个有 then 方法的对象或者是函数,行为遵循本规范
  2. thenable 是一个有 then 方法的对象或者是函数
  3. valuepromise 状态成功时的值,也就是 resolve 的参数, 包括各种数据类型, 也包括 undefined/thenable 或者是 promise
  4. reasonpromise 状态失败时的值, 也就是 reject 的参数, 表示拒绝的原因
  5. exception 是一个使用 throw 抛出的异常值

Promise Status

promise 应该有三种状态. 要注意他们之间的流转关系.

  1. pending

    1.1 初始的状态, 可改变.
    1.2 一个 promiseresolve 或者 reject 前都处于这个状态。
    1.3 可以通过 resolve -> fulfilled 状态;
    1.4 可以通过 reject -> rejected 状态;

  2. fulfilled

    2.1 最终态, 不可变.
    2.2 一个 promiseresolve 后会变成这个状态.
    2.3 必须拥有一个 value

  3. rejected

    3.1 最终态, 不可变.
    3.2 一个 promisereject 后会变成这个状态
    3.3 必须拥有一个 reason

Tips: 总结一下, 就是 promise 的状态流转是这样的

pending -> resolve(value) -> fulfilled
pending -> reject(reason) -> rejected

then

promise 应该提供一个 then 方法, 用来访问最终的结果, 无论是 value 还是 reason.

promise.then(onFulfilled, onRejected);
复制代码
  1. 参数要求

    1.1 onFulfilled 必须是函数类型, 如果不是函数, 应该被忽略.
    1.2 onRejected 必须是函数类型, 如果不是函数, 应该被忽略.

  2. onFulfilled 特性

    2.1 在 promise 变成 fulfilled 时,应该调用 onFulfilled, 参数是 value
    2.2 在 promise 变成 fulfilled 之前, 不应该被调用.
    2.3 只能被调用一次(所以在实现的时候需要一个变量来限制执行次数)

  3. onRejected 特性

    3.1 在 promise 变成 rejected 时,应该调用 onRejected, 参数是 reason
    3.2 在 promise 变成 rejected 之前, 不应该被调用.
    3.3 只能被调用一次(所以在实现的时候需要一个变量来限制执行次数)

  4. onFulfilled 和 onRejected 应该是微任务

    这里用 queueMicrotask 来实现微任务的调用.

  5. then 方法可以被调用多次

    5.1 promise 状态变成 fulfilled 后,所有的 onFulfilled 回调都需要按照 then 的顺序执行, 也就是按照注册顺序执行(所以在实现的时候需要一个数组来存放多个 onFulfilled 的回调)
    5.2 promise 状态变成 rejected 后,所有的 onRejected 回调都需要按照 then 的顺序执行, 也就是按照注册顺序执行(所以在实现的时候需要一个数组来存放多个 onRejected 的回调)

  6. 返回值

    then 应该返回一个 promise

    promise2 = promise1.then(onFulfilled, onRejected);
    复制代码

    6.1 onFulfilled 或 onRejected 执行的结果为 x, 调用 resolvePromise( 这里大家可能难以理解, 可以先保留疑问, 下面详细讲一下 resolvePromise 是什么东西 )
    6.2 如果 onFulfilled 或者 onRejected 执行时抛出异常 e, promise2 需要被 reject
    6.3 如果 onFulfilled 不是一个函数, promise2 以 promise1 的 value 触发 fulfilled
    6.4 如果 onRejected 不是一个函数, promise2 以 promise1 的 reason 触发 rejected

  7. resolvePromise

    resolvePromise(promise2, x, resolve, reject);
    复制代码

    7.1 如果 promise2 和 x 相等,那么 reject TypeError
    7.2 如果 x 是一个 promsie
    如果 x 是 pending 态,那么 promise 必须要在 pending,直到 x 变成 fulfilled or rejected.
    如果 x 被 fulfilled, fulfill promise with the same value.
    如果 x 被 rejected, reject promise with the same reason.
    7.3 如果 x 是一个 object 或者 是一个 function
    let then = x.then.
    如果 x.then 这步出错,那么 reject promise with e as the reason.
    如果 then 是一个函数,then.call(x, resolvePromiseFn, rejectPromise)
    resolvePromiseFn 的 入参是 y, 执行 resolvePromise(promise2, y, resolve, reject);
    rejectPromise 的 入参是 r, reject promise with r.
    如果 resolvePromise 和 rejectPromise 都调用了,那么第一个调用优先,后面的调用忽略。
    如果调用 then 抛出异常 e
    如果 resolvePromise 或 rejectPromise 已经被调用,那么忽略
    则,reject promise with e as the reason
    如果 then 不是一个 function. fulfill promise with x.

实现一个 Promise

一步步实现

平常用 promise 的时候, 是通过 new 关键字来 new Promise(), 那就用 class 来实现一下吧.

class YuPromise {
  constructor() {}
}
复制代码

定义三种状态类型

const PENDING = "pending";
const FULFILLED = "fulfilled";
const REJECTED = "rejected";
复制代码

设置初始状态

class YuPromise {
  constructor() {
    // 初始状态为pending
    this.status = PENDING;
    this.value = null;
    this.reason = null;
  }
}
复制代码

resolvereject 方法

  1. 根据刚才的规范, 这两个方法是要更改 status 的, 从 pending 改到 fulfilled/rejected.
  2. 注意两个函数的入参分别是 value 和 reason.
class YuPromise {
  constructor() {
    // 初始状态为pending
    this.status = PENDING;
    this.value = null;
    this.reason = null;
  }

  resolve(value) {
    if (this.status === PENDING) {
      this.value = value;
      this.status = FULFILLED;
    }
  }

  reject(reason) {
    if (this.status === PENDING) {
      this.reason = reason;
      this.status = REJECTED;
    }
  }
}
复制代码

加一下 promise 入参

  1. 入参是一个函数, 函数接收 resolve 和 reject 两个参数.
  2. 注意在初始化 promise 的时候, 就要执行这个函数, 并且有任何报错都要通过 reject 抛出去
class YuPromise {
  constructor(fn) {
    // 初始状态为pending
    this.status = PENDING;
    this.value = null;
    this.reason = null;

    try {
      // 绑定this, 使用当前promise实例上的 resolve 和 reject
      fn(this.resolve.bind(this), this.reject.bind(this));
    } catch (e) {
      this.reject(e);
    }
  }

  resolve(value) {
    if (this.status === PENDING) {
      this.value = value;
      this.status = FULFILLED;
    }
  }

  reject(reason) {
    if (this.status === PENDING) {
      this.reason = reason;
      this.status = REJECTED;
    }
  }
}
复制代码

接下来来实现一下关键的 then 方法

  1. then 接收两个参数, onFulfilled 和 onRejected
then(onFulfilled, onRejected) {}
复制代码
  1. 检查并处理参数, 之前提到的如果不是 function, 就忽略. 这个忽略指的是原样返回 value 或者 reason.
isFunction(param) {
    return typeof param === 'function';
}

then(onFulfilled, onRejected) {
    const realOnFulfilled = this.isFunction(onFulfilled) ? onFulfilled : (value) => {
        return value
    }
    const realOnRejected = this.isFunction(onRejected) ? onRejected : (reason) => {
        throw reason;
    };
}
复制代码
  1. .then 的返回值整体是一个 promise, 用 promise 来包裹一下.
then(onFulfilled, onRejected) {
    const realOnFulfilled = this.isFunction(onFulfilled) ? onFulfilled : (value) => {
        return value
    }
    const realOnRejected = this.isFunction(onRejected) ? onRejected : (reason) => {
        throw reason;
    };
    const promise2 = new YuPromise((resolve, reject) => {})
    return promise2
}

复制代码
  1. 根据当前 promise 的状态, 调用不同的函数
then(onFulfilled, onRejected) {
    const realOnFulfilled = this.isFunction(onFulfilled) ? onFulfilled : (value) => {
        return value
    }
    const realOnRejected = this.isFunction(onRejected) ? onRejected : (reason) => {
        throw reason;
    };
    const promise2 = new YuPromise((resolve, reject) => {
        switch (this.status) {
            // 这样写是在 then 函数调用瞬间会执行的, 主要是有一些直接决议的场景, 比如resolve(1)
            case FULFILLED: {
                realOnFulfilled()
                break;
            }
            case REJECTED: {
                realOnRejected()
                break;
            }
        }
    })
    return promise2

}
复制代码
  1. 这样写, 是在 then 函数被调用的瞬间就会执行. 那这时候如果 status 还没变成 fulfilled 或者 rejected 怎么办, 很有可能还是 pending 的. 所以我们需要一个状态的监听机制, 当状态变成 fulfilled 或者 rejected 后, 再去执行 callback.
  • 那么我们首先要拿到所有的 callback, 然后才能在某个时机去执行他. 新建两个数组, 来分别存储成功和失败的回调, 调用 then 的时候, 如果还是 pending 就存入数组.

    // 使用数组, 主要是可以在一个 promise 实例上 .then 多次
      this.fulfilledCallbackList = [];
      this.rejectedCallbackList = [];
    
    then(onFulfilled, onRejected) {
    const realOnFulfilled = this.isFunction(onFulfilled) ? onFulfilled : (value) => {
        return value
    }
    const realOnRejected = this.isFunction(onRejected) ? onRejected : (reason) => {
        throw reason;
    };
    const promise2 = new YuPromise((resolve, reject) => {
        switch (this.status) {
            case FULFILLED: {
                realOnFulfilled()
                break;
            }
            case REJECTED: {
                realOnRejected()
                break;
            }
            case PENDING: {
                this.fulfilledCallbackList.push(realOnFulfilled)
                this.rejectedCallbackList.push(realOnRejected)
            }
        }
    })
    return promise2
    
    }
    复制代码

在 status 发生变化的时候, 就执行所有的回调. 这里用一下 es6 的 getter 和 setter. 这样更符合语义, 当 status 改变时, 去做什么事情. (当然也可以顺序执行, 在给 status 赋值后, 下面再加一行 forEach)

// 防止 getter 时套娃
this._status = PENDING;

get status() {
    return this._status;
}

set status(newStatus) {
    this._status = newStatus;
    switch (newStatus) {
        case FULFILLED: {
            this.fulfilledCallbackList.forEach(callback => {
                callback(this.value);
            });
            break;
        }
        case REJECTED: {
            this.rejectedCallbackList.forEach(callback => {
                callback(this.reason);
            });
            break;
        }
    }
}
复制代码

then 的返回值

上面只是简单说了下, then 的返回值是一个 Promise, 那么接下来具体讲一下返回 promise 的 value 和 reason 是什么.

  1. 如果 onFulfilled 或者 onRejected 抛出一个异常 e ,则 promise2 必须拒绝执行,并返回拒因 e。(这样的话, 我们就需要手动 catch 代码,遇到报错就 reject)
then(onFulfilled, onRejected) {
    const realOnFulfilled = this.isFunction(onFulfilled) ? onFulfilled : (value) => {
        return value
    }
    const realOnRejected = this.isFunction(onRejected) ? onRejected : (reason) => {
        throw reason;
    };
    const promise2 = new YuPromise((resolve, reject) => {
         const fulfilledMicrotask = () => {
            try {
                realOnFulfilled(this.value);
            } catch (e) {
                reject(e)
            }
        };
        const rejectedMicrotask = () => {
            try {
                realOnRejected(this.reason);
            } catch (e) {
                reject(e);
            }
        }

        switch (this.status) {
            case FULFILLED: {
                fulfilledMicrotask()
                break;
            }
            case REJECTED: {
                rejectedMicrotask()
                break;
            }
            case PENDING: {
              this.fulfilledCallbackList.push(realOnFulfilled)
              this.rejectedCallbackList.push(realOnRejected)
            }
        }
    })
    return promise2
}
复制代码
  1. 如果 onFulfilled 不是函数且 promise1 成功执行, promise2 必须成功执行并返回相同的值

  2. 如果 onRejected 不是函数且 promise1 拒绝执行, promise2 必须拒绝执行并返回相同的据因。

需要注意的是,如果 promise1 的 onRejected 执行成功了,promise2 应该被 resolve

这里咱们其实已经在参数检查的时候做过了, 也就是这段代码

const realOnFulfilled = this.isFunction(onFulfilled)
  ? onFulfilled
  : (value) => {
      return value;
    };
const realOnRejected = this.isFunction(onRejected)
  ? onRejected
  : (reason) => {
      throw reason;
    };
复制代码
  1. 如果 onFulfilled 或者 onRejected 返回一个值 x ,则运行 resolvePromise 方法
then(onFulfilled, onRejected) {
    const realOnFulfilled = this.isFunction(onFulfilled) ? onFulfilled : (value) => {
        return value
    }
    const realOnRejected = this.isFunction(onRejected) ? onRejected : (reason) => {
        throw reason;
    };
    const promise2 = new YuPromise((resolve, reject) => {
        const fulfilledMicrotask = () => {
            try {
                realOnFulfilled(this.value);
            } catch (e) {
                reject(e)
            }
        };
        const rejectedMicrotask = () => {
            try {
                realOnRejected(this.reason);
            } catch (e) {
                reject(e);
            }
        }

        switch (this.status) {
            case FULFILLED: {
                fulfilledMicrotask()
                break;
            }
            case REJECTED: {
                rejectedMicrotask()
                break;
            }
            case PENDING: {
               this.fulfilledCallbackList.push(realOnFulfilled)
               this.rejectedCallbackList.push(realOnRejected)
            }
        }
    })
    return promise2
}
复制代码

resolvePromise

resolvePromise(promise2, x, resolve, reject) {
    // 如果 newPromise 和 x 指向同一对象,以 TypeError 为据因拒绝执行 newPromise
    // 这是为了防止死循环
    if (promise2 === x) {
        return reject(new TypeError('The promise and the return value are the same'));
    }

    if (x instanceof YuPromise) {
        // 如果 x 为 Promise ,则使 newPromise 接受 x 的状态
        // 也就是继续执行x,如果执行的时候拿到一个y,还要继续解析y
        queueMicrotask(() => {
            x.then((y) => {
                this.resolvePromise(promise2, y, resolve, reject);
            }, reject);
        })
    } else if (typeof x === 'object' || this.isFunction(x)) {
        // 如果 x 为对象或者函数
        if (x === null) {
            // null也会被判断为对象
            return resolve(x);
        }

        let then = null;

        try {
            // 把 x.then 赋值给 then
            then = x.then;
        } catch (error) {
            // 如果取 x.then 的值时抛出错误 e ,则以 e 为据因拒绝 promise
            return reject(error);
        }

        // 如果 then 是函数
        if (this.isFunction(then)) {
            let called = false;
            // 将 x 作为函数的作用域 this 调用
            // 传递两个回调函数作为参数,第一个参数叫做 resolvePromise ,第二个参数叫做 rejectPromise
            try {
                then.call(
                    x,
                    // 如果 resolvePromise 以值 y 为参数被调用,则运行 resolvePromise
                    (y) => {
                        // 需要有一个变量called来保证只调用一次.
                        if (called) return;
                        called = true;
                        this.resolvePromise(promise2, y, resolve, reject);
                    },
                    // 如果 rejectPromise 以据因 r 为参数被调用,则以据因 r 拒绝 promise
                    (r) => {
                        if (called) return;
                        called = true;
                        reject(r);
                    });
            } catch (error) {
                // 如果调用 then 方法抛出了异常 e:
                if (called) return;

                // 否则以 e 为据因拒绝 promise
                reject(error);
            }
        } else {
            // 如果 then 不是函数,以 x 为参数执行 promise
            resolve(x);
        }
    } else {
        // 如果 x 不为对象或者函数,以 x 为参数执行 promise
        resolve(x);
    }
}
复制代码

onFulfilled 和 onRejected 是微任务

使用 queueMicrotask 包裹执行函数

const fulfilledMicrotask = () => {
  queueMicrotask(() => {
    try {
      const x = realOnFulfilled(this.value);
      this.resolvePromise(promise2, x, resolve, reject);
    } catch (e) {
      reject(e);
    }
  });
};
const rejectedMicrotask = () => {
  queueMicrotask(() => {
    try {
      const x = realOnRejected(this.reason);
      this.resolvePromise(promise2, x, resolve, reject);
    } catch (e) {
      reject(e);
    }
  });
};
复制代码

简单写点代码测试一下

const test = new YuPromise((resolve, reject) => {
  setTimeout(() => {
    resolve(111);
  }, 1000);
}).then(console.log);

console.log("case1", test);

setTimeout(() => {
  console.log("case2", test);
}, 2000);

// 打印一下内容, 符合预期
// case1 YuPromise {
//     _status: 'pending',
//     value: null,
//     reason: null,
//     fullfilledList: [],
//     rejectedList: [] }
// case2 YuPromise {
//     _status: 'fullfilled',
//     value: undefined,
//     reason: null,
//     fullfilledList: [],
//     rejectedList: [] }
复制代码

为什么可以调用.then, 不可以调用.catch 呢? 因为我们并没有在类里面声明 catch 方法, 添加 catch 方法

catch (onRejected) {
    return this.then(null, onRejected);
}
复制代码

promise.resolve

将现有对象转为 Promise 对象,如果 Promise.resolve 方法的参数,不是具有 then 方法的对象(又称 thenable 对象),则返回一个新的 Promise 对象,且它的状态为 fulfilled。
注意这是一个静态方法, 因为是通过 Promise.resolve 调用的, 而不是通过实例去调用的.

static resolve(value) {
    if (value instanceof YuPromise) {
        return value;
    }

    return new YuPromise((resolve) => {
        resolve(value);
    });
}
复制代码

promise.reject

返回一个新的 Promise 实例,该实例的状态为 rejected。Promise.reject 方法的参数 reason,会被传递给实例的回调函数。

static reject(reason) {
    return new YuPromise((resolve, reject) => {
        reject(reason);
    });
}
复制代码

promise.race

const p = Promise.race([p1, p2, p3]);

该方法是将多个 Promise 实例,包装成一个新的 Promise 实例。
只要 p1、p2、p3 之中有一个实例率先改变状态,p 的状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给 p 的回调函数。

static race(promiseList) {
    return new YuPromise((resolve, reject) => {
        const length = promiseList.length;

        if (length === 0) {
            return resolve();
        } else {
            for (let i = 0; i < length; i++) {
                YuPromise.resolve(promiseList[i]).then(
                    (value) => {
                        return resolve(value);
                    },
                    (reason) => {
                        return reject(reason);
                    });
            }
        }
    });

}
复制代码

写段测试代码

const test = new YuPromise((resolve, reject) => {
  setTimeout(() => {
    resolve(111);
  }, 1000);
});

const test2 = new YuPromise((resolve, reject) => {
  setTimeout(() => {
    resolve(222);
  }, 2000);
});

const test3 = new YuPromise((resolve, reject) => {
  setTimeout(() => {
    resolve(333);
  }, 3000);
});

YuPromise.race([test, test2, test3]).then(console.log); // 打印了 111, 符合预期
复制代码

完整代码

const PENDING = "pending";
const FULFILLED = "fulfilled";
const REJECTED = "rejected";

// queueMicrotask pollfill
if (typeof queueMicrotask !== "function") {
  queueMicrotask = function (callback) {
    Promise.resolve()
      .then(callback)
      .catch((e) => {
        setTimeout(() => {
          throw e;
        });
      });
  };
}

class YuPromise {
  constructor(fn) {
    this.status = PENDING;
    this._status = PENDING;
    this.value = null;
    this.reason = null;
    this.fulfilledCallbackList = [];
    this.rejectedCallbackList = [];
    try {
      fn(this.resolve.bind(this), this.reject.bind(this));
    } catch (e) {
      this.reject(e);
    }
  }

  get status() {
    return this._status;
  }

  set status(newStatus) {
    this._status = newStatus;
    switch (newStatus) {
      case FULFILLED: {
        this.fulfilledCallbackList.forEach((callback) => {
          callback(this.value);
        });
        break;
      }
      case REJECTED: {
        this.rejectedCallbackList.forEach((callback) => {
          callback(this.reason);
        });
        break;
      }
    }
  }

  resolve(value) {
    if (this.status !== PENDING) return;
    this.value = value;
    this.status = FULFILLED;
  }

  reject(reason) {
    if (this.status !== PENDING) return;
    this.reason = reason;
    this.status = REJECTED;
  }

  then(onFulfilled, onRejected) {
    const realOnFulfilled = this.isFunction(onFulfilled)
      ? onFulfilled
      : (value) => {
          return value;
        };
    const realOnRejected = this.isFunction(onRejected)
      ? onRejected
      : (reason) => {
          throw reason;
        };
    const promise2 = new YuPromise((resolve, reject) => {
      const fulfilledMicrotask = () => {
        queueMicrotask(() => {
          try {
            const x = realOnFulfilled(this.value);
            this.resolvePromise(promise2, x, resolve, reject);
          } catch (e) {
            reject(e);
          }
        });
      };
      const rejectedMicrotask = () => {
        queueMicrotask(() => {
          try {
            const x = realOnRejected(this.reason);
            this.resolvePromise(promise2, x, resolve, reject);
          } catch (e) {
            reject(e);
          }
        });
      };

      switch (this.status) {
        case FULFILLED: {
          fulfilledMicrotask();
          break;
        }
        case REJECTED: {
          rejectedMicrotask();
          break;
        }
        case PENDING: {
          this.fulfilledCallbackList.push(fulfilledMicrotask);
          this.rejectedCallbackList.push(rejectedMicrotask);
        }
      }
    });
    return promise2;
  }

  catch(onRejected) {
    return this.then(null, onRejected);
  }

  isFunction(param) {
    return typeof param === "function";
  }

  resolvePromise(promise2, x, resolve, reject) {
    // 如果 newPromise 和 x 指向同一对象,以 TypeError 为据因拒绝执行 newPromise
    // 这是为了防止死循环
    if (promise2 === x) {
      return reject(
        new TypeError("The promise and the return value are the same")
      );
    }

    if (x instanceof YuPromise) {
      // 如果 x 为 Promise ,则使 newPromise 接受 x 的状态
      // 也就是继续执行x,如果执行的时候拿到一个y,还要继续解析y
      queueMicrotask(() => {
        x.then((y) => {
          this.resolvePromise(promise2, y, resolve, reject);
        }, reject);
      });
    } else if (typeof x === "object" || this.isFunction(x)) {
      // 如果 x 为对象或者函数
      if (x === null) {
        // null也会被判断为对象
        return resolve(x);
      }

      let then = null;

      try {
        // 把 x.then 赋值给 then
        then = x.then;
      } catch (error) {
        // 如果取 x.then 的值时抛出错误 e ,则以 e 为据因拒绝 promise
        return reject(error);
      }

      // 如果 then 是函数
      if (this.isFunction(then)) {
        let called = false;
        // 将 x 作为函数的作用域 this 调用
        // 传递两个回调函数作为参数,第一个参数叫做 resolvePromise ,第二个参数叫做 rejectPromise
        try {
          then.call(
            x,
            // 如果 resolvePromise 以值 y 为参数被调用,则运行 resolvePromise
            (y) => {
              // 需要有一个变量called来保证只调用一次.
              if (called) return;
              called = true;
              this.resolvePromise(promise2, y, resolve, reject);
            },
            // 如果 rejectPromise 以据因 r 为参数被调用,则以据因 r 拒绝 promise
            (r) => {
              if (called) return;
              called = true;
              reject(r);
            }
          );
        } catch (error) {
          // 如果调用 then 方法抛出了异常 e:
          if (called) return;

          // 否则以 e 为据因拒绝 promise
          reject(error);
        }
      } else {
        // 如果 then 不是函数,以 x 为参数执行 promise
        resolve(x);
      }
    } else {
      // 如果 x 不为对象或者函数,以 x 为参数执行 promise
      resolve(x);
    }
  }

  static resolve(value) {
    if (value instanceof YuPromise) {
      return value;
    }

    return new YuPromise((resolve) => {
      resolve(value);
    });
  }

  static reject(reason) {
    return new YuPromise((resolve, reject) => {
      reject(reason);
    });
  }

  static race(promiseList) {
    return new YuPromise((resolve, reject) => {
      const length = promiseList.length;

      if (length === 0) {
        return resolve();
      } else {
        for (let i = 0; i < length; i++) {
          YuPromise.resolve(promiseList[i]).then(
            (value) => {
              return resolve(value);
            },
            (reason) => {
              return reject(reason);
            }
          );
        }
      }
    });
  }
}
复制代码

实现 Promise 周边

封装一个简单的 xhr 请求

  1. 使用 cb 形式
function ajax(url, success, fail) {
    const client = new XMLHttpRequest();
    client.open('GET', url);
    client.onreadystatechange = function () {
        if(this.readystate !== 4) {
            return;
        }
        if(this.status === 200) {
            success(this.response);
        }else {
            fail(new Error(this.statusText));
        }
    }
    client.send();
}

// 如果请求2依赖请求1的结果, 那就在请求1的回调中再实现请求2, 一层层下去, 形成回调地狱
ajax('/test.json', function (res) {
    console.log('success', res);
}, function(statusText) {
    console.log('error', statusText);
});
复制代码
  1. 使用 promise 进行封装(ps: 通常用 promise 封装呢会使用 Async 的词根, 跟 Node.js 学的)
function ajaxAsync(url) {
  return new Promise((resolve, reject) => {
    const client = new XMLHttpRequest();
    client.open("GET", url);
    client.onreadystatechange = function () {
      if (this.readystate !== 4) {
        return;
      }
      if (this.status === 200) {
        resolve(this.response);
      } else {
        reject(new Error(this.statusText));
      }
    };
    client.send();
  });
}

ajaxAsync("/test.json")
  .then((res) => {
    console.log("success", res);
  })
  .catch((err) => {
    console.log("error", err);
  });
复制代码

更改为 promise 的步骤呢, 就是内部构造 promise 实例, 在原来执行回调函数的地方执行对应的更改 promise 状态的函数即可

实现一个 Promise.chain, 一条一条执行

实现思路:

  1. for of 配合 async await
  2. 数组的 reduce 方法(进行转换, 转换成 promise1.then(() => {return promise2}) 的形式)
// 题目
function promiseCreator1() {
  return new Promise((resolve) => {
    setTimeout(resolve, 1000);
  });
}

function promiseCreator2() {
  return new Promise((resolve) => {
    setTimeout(resolve, 1000);
  });
}

const promiseCreatorList = [promiseCreator1, promiseCreator2];
复制代码

for of 配合 async await

async function main() {
  async function forOfLoop() {
    for (const promiseInstance of promiseCreatorList) {
      await promiseInstance();
    }
  }
  await forOfLoop();
}
main();
复制代码

数组的 reduce 方法

// reduce 可以接收第二个参数, 其实我们可以给它一个空的 Promise 实例, 那这样在迭代过程中就可以保证都是 Promise 实例
const promiseChain = promiseCreatorList.reduce((prev, cur) => {
  return prev.then(cur);
}, Promise.resolve());

// 最后一次返回的值肯定也是一个 Promise 实例
promiseChain.then(() => {
  console.log("end");
});
复制代码

Promise.allSeltted

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

function PromiseAllSeltted(promiseArray) {
  return new Promise(function (resolve, reject) {
    if (!Array.isArray(promiseArray)) {
      return reject(new TypeError("arguments muse be an array"));
    }
    let counter = 0;
    let promiseNum = promiseArray.length;
    let resolvedArray = [];
    for (let i = 0; i < promiseNum; i++) {
      Promise.resolve(promiseArray[i])
        .then((value) => {
          resolvedArray[i] = {
            value,
            status: "fulfilled",
          };
        })
        .catch((reason) => {
          resolvedArray[i] = {
            reason,
            status: "rejected",
          };
        })
        .finally(() => {
          counter++;
          if (counter == promiseNum) {
            resolve(resolvedArray);
          }
        });
    }
  });
}

// 测试
const pro1 = new Promise((res, rej) => {
  setTimeout(() => {
    res("1");
  }, 1000);
});
const pro2 = new Promise((res, rej) => {
  setTimeout(() => {
    rej("2");
  }, 2000);
});
const pro3 = new Promise((res, rej) => {
  setTimeout(() => {
    res("3");
  }, 3000);
});

const proAll = PromiseAllSeltted([pro1, pro2, pro3])
  .then((res) => console.log(res))
  .catch((e) => {
    console.log(e);
  });
复制代码

手写一个 Promise.all

它接收一个数组, 数组里面是 Promise 或者是常量, 返回一个 Promise, 它会使用 Promise.resolve 对数组元素包裹一层, 把非 Promise 元素也转变为 Promise, 当数组中所有的 Promise 执行完了, 就会 resolve 掉, 当有一个 Promise 报错了, 就会 reject 掉

当一个 Promise 报错后, 其他 Promise 会执行吗?
会的, Promise 在实例化的时候就执行完了, .then 只是为了拿到结果

function PromiseAll(promiseArray) {
  // 首先肯定是返回一个 promise
  return new Promise((resolve, reject) => {
    // 判断一下是否是数组
    if (!Array.isArray(promiseArray)) {
      return reject(new Error("传入的参数需要是一个数组"));
    }
    let resArr = [];
    let count = 0; // 记录执行返回的结果数量, 因为 resArr[i], 可能会出现 undefined, 可以举例子说明, 比如数组一开始为空, 如果 resArr[10] = 1, 这个时候的 resArr.length = 11, js 会把空间留出来
    const promiseLen = promiseArray.length;
    // ps: 自己百度的, 如果是数组这种采用下标访问的, 使用 for, 效率已经够高了, 如果是链表结构的, 可以使用 forEach
    for (let i = 0; i < promiseLen; i++) {
      // Promise.resolve 包裹一层可以把一些常量也转为 promise
      Promise.resolve(promiseArray[i])
        .then((res) => {
          // 这种方式不对, 忽略了 Promise.all 的特性, Promise.all 接收的数组元素是什么顺序, 返回的结果也是什么顺序, 但是使用 push 有可能某一个 promise 执行得更快, 走在了前面 push
          // resArr.push(res);
          resArr[i] = res;
          count++;
          // 千万记住不能放在 .then 外面, 因为放外面是同步执行的
          if (count === promiseLen) {
            resolve(resArr);
          }
        })
        .catch((err) => reject(err));
    }
  });
}
复制代码

实现一个 cachePromise

所有的 Promise 在实例化的时候就已经执行了

这道题目的意义是, 比如要去调用一个接口, 这个请求调用的是一个常量, 可能很多页面都使用到了, 那如果没有全局的状态管理, 那每个页面都调用一遍, 执行一遍, 也会比较浪费服务器的性能

那这里使用装饰器来实现一遍

装饰器科普

target 是所属类的原型
name 是修饰的属性名称
descriptor 是修饰的属性描述符号, 如 writable 这些配置等等, value 是属性值, 参考 Object.defineProperty

ES6 中定义一个类的写法,其实只是一个语法糖,而实际上当我们给一个类添加一个属性的时候,会调用到 Object.defineProperty 这个方法,它会接受三个参数:target 、name 和 descriptor

// 属性描述符
let descriptor = {
  value: function () {
    console.log("meow ~");
  },
  enumerable: false,
  configurable: true,
  writable: true,
};

// 经过 readonly 装饰器修饰后的属性描述符
descriptor = readonly(Cat.prototype, "say", descriptor) || descriptor;

// ES6中 给类添加属性
Object.defineProperty(Cat.prototype, "say", descriptor);
复制代码

当装饰器作用于类本身的时候,我们操作的对象也是这个类本身,而当装饰器作用于类的某个具体的属性的时候,我们操作的对象既不是类本身,也不是类的属性,而是它的描述符(descriptor),而描述符里记录着我们对这个属性的全部信息,所以,我们可以对它自由的进行扩展和封装,最后达到的目的呢,就和之前说过的装饰器的作用是一样的。

当然,如果你喜欢的话,也可以直接在 target 上进行扩展和封装

cachePromise 实现

刷新肯定就没了, 那没什么办法, 装饰器是 es7 的, 如果不支持可能要安装一个 babel 插件

有缓存, 就要考虑时效, 没有任何一个缓存是永久有效的, 在这里在去标识一下过期时间啊

const cacheMap = new Map();

function enableCache(target, name, descriptor) {
  const val = descriptor.value;
  descriptor.value = async function (...args) {
    const cacheKey = `${name}${JSON.stringify(args)}`;
    if (!cacheMap.get(cacheKey)) {
      const cacheValue = Promise.resolve(val.apply(this, args)).catch((_) => {
        cacheMap.set(cacheKey, null);
      });
      cacheMap.set(cacheKey, cacheValue);
    }
    return cacheMap.get(cacheKey);
  };
  return descriptor;
}

class PromiseClass {
  @enableCache()
  static async getInfo() {}
}

PromiseClass.getInfo.then(...).catch(...);
复制代码

Promise.race 的使用: 如果你的页面上有巨量的图片要展示, 那除了懒加载的形式, 还有什么形式可以来限制下同时加载的数量?(代码题, 写代码来实现并发的控制)

function limitLoad(urls, handler, limit) {
}

function loadImg(url) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve();
    }, url.time);
  });
}

const urls = [{ info: "info", time: 200 }...];

limitLoad(urls, loadImg, 3);

// 1, 2, 3, 4, 5, 6, 7
// 先请求 1, 2, 3, 如果其中有一个加载完了, 就从剩下的选一个接着请求
// 保证一个时间就是 3 个同时在请求
// 利用 Promise.race, 竞态, 只要有一个完成了, 会直接 resolve
function limitLoad(urls, handler, limit) {
    // 不能对外部产生影响, 要进行拷贝
    const sequeue = [].cancat(urls);

    // 截取三个, 注意这里用的 splice(), 会改变原数组, 所以下面遍历的时候其实这里使用的三个是不存在的了
    const promises = sequeue.splice(0, 3).map((url, index) => {
        return handler(url).then(() => {
            // 记录当前的 index, 用于下面填补
            return index;
        });
    })

    // 每次第一个完成了就会 resolve 掉
    const p = Promise.race(promises);

    // 这是一个同步 for 循环, 开启 .then().then()
    for(let i = 0, len= sequeue.length; i < len; i ++) {
        p = p.then((res) => {
            promises[res] = handler(sequeue[i]).then(() => {
                return res;
            })
            return Promise.race(promises);
        })
    }
}

// 思路:
// 首先截取三个去发送请求, 这三个请求就发出去了, 然后当有一个请求完成时, 就 resolve 了
// 然后在当前 resolve 的位置重新发出一个请求, 再开启一个竞态
// 这个遍历的话其实是链式调用, 会 .then().then().then(), 都是根据最先请求完成的位置开启的, 完成开启下一个
// 使用 Promise.race() 来实现并行, Promise 会在初始化的时候就执行了, 只不过是整个 Promise 会返回一个结果, Promise 数组里面的 Promise 还是会进行执行的
复制代码

看题说话

为什么 promise resolve 了一个 value, 最后输出的 value 值却是 undefined

const test = new YuPromise((resolve, reject) => {
  setTimeout(() => {
    resolve(111);
  }, 1000);
}).then((value) => {
  console.log("then");
});

setTimeout(() => {
  console.log(test);
}, 3000);
复制代码

因为现在这种写法, 相当于在.then 里 return undefined, 所以最后的 value 是 undefined.
如果显式 return 一个值, 就不是 undefined 了;比如 return value.

.then 返回的是一个新 Promise, 那么原来 promise 实现的时候, 用数组来存回调函数有什么意义?

这个问题提出的时候, 应该是有一个假定条件, 就是链式调用的时候.

这个时候, 每一个.then 返回的都是一个新 promise, 所以每次回调数组 fulfilledCallbackList 都是空数组.

针对这种情况, 确实用数组来存储回调没意义, 完全可以就用一个变量来存储。

const test = new YuPromise((resolve, reject) => {
  setTimeout(() => {
    resolve(111);
  }, 1000);
})
  .then((value) => {})
  .then(() => {});
复制代码

但是还有一种 promise 使用的方式, 这种情况下, promise 实例是同一个, 数组的存在就有了意义

const test = new YuPromise((resolve, reject) => {
  setTimeout(() => {
    resolve(111);
  }, 1000);
});

test.then(() => {});
test.then(() => {});
test.then(() => {});
test.then(() => {});
复制代码

为什么我在 catch 的回调里, 打印 promise, 显示状态是 pending

const test = new YuPromise((resolve, reject) => {
  setTimeout(() => {
    reject(111);
  }, 1000);
}).catch((reason) => {
  console.log("报错" + reason);
  console.log(test);
});

setTimeout(() => {
  console.log(test);
}, 3000);
复制代码
  1. catch 函数会返回一个新的 promise, 而 test 就是这个新 promise
  2. catch 的回调里, 打印 promise 的时候, 整个回调还并没有执行完成(所以此时的状态是 pending), 只有当整个回调完成了, 才会更改状态
  3. catch 的回调函数, 如果成功执行完成了, 会改变这个新 Promise 的状态为 fulfilled

这段代码会打印什么?(注意 node 版本不同会有所区别)

const promise = new YuPromise((resolve) => {
  setTimeout(() => {
    resolve();
  }, 1000);
}).then(() => {
  console.log("case1", promise);
});

setTimeout(() => {
  console.log("case2", promise);
}, 1000);
复制代码

Promise 实例化的时候是同步的, Promise 里的 setTimeout 先加入任务队列, 称为 s1 吧
再是全局的 setTimeout 加入任务队列, 称为 s2 吧
然后 s1 执行, resolve 是微任务, 加入微任务队列, 会在当前宏任务把控制权交还给浏览器前先清空微任务队列, 然后 case1 先打印, 状态为 pending
再执行 s2, 由于 then 中 相当于是返回了 undefined, s2 这里状态是 fullfilled

  1. 在浏览器中会是 case 1 -> case2, 没毛病
  2. 如果是在 node 环境运行, node 版本不同会有所差别, 如以下命令, node 版本为 10.16.3, case2 先执行了, node 版本为 14.17.1 时, 事件循环机制跟浏览器保持了一致, case1 先执行
// xxx@MacBook-Pro Promise % node -v
// v10.16.3
// xxx@MacBook-Pro Promise % node Promise.js
// case2
// case1
// xxx@MacBook-Pro Promise % nvm use 14.17.1
// Now using node v14.17.1 (npm v6.14.13)
// xxx@MacBook-Pro Promise % node -v
// v14.17.1
// xxx@MacBook-Pro Promise % node Promise.js
// case1
// case2
复制代码

其他问题

  1. 图片的懒加载是不是用的 Promise?

图片的懒加载通常是使用浏览器原生的 observer 来监听元素是否进入可视区, 替换元素的 src 来达到懒加载的目的

  1. getter 中没有什么逻辑, 为什么要写 getter 呢?

因为 getter 和 setter 是成对出现的

  1. 为什么要用一个私有变量呢?

因为如果 getter 中访问的是 this.status, 那就死循环, 套娃了, 使用一个私有变量_status 是为了防止套娃

  1. 为什么使用 queueMicrotask 包裹而不是 setTimeout ?

因为 setTimeout 是一个经典的宏任务

  1. 为什么 Promise.race 遍历, 传入 resolve, 不会多次执行?

因为当 resolve 或者 reject 异步操作执行了就会被锁.

  1. 为什么用数组来存?

因为数组可以存多个函数, 而且也满足顺序的条件

  1. then 每次 return 的都是一个新的 Promise, 新的 Promise 每次都会初始化数组为空的, 那你用数组存的意义在哪里? 为什么不用一个变量来存?

因为当前 promise 实例可以被多次调用, 不一定是链式调用

因为可以

promise.then(() => {});
promise.then(() => {});
promise.then(() => {});
promise.then(() => {});
promise.then(() => {});
复制代码
  1. resolvePromise 是为了什么?

为了解析 x 的值

  1. race 方法 list 中第一个 promise 被决议之后状态就变更了 list 剩下的 promise 也会执行 他们的结果就不了了之了么?

是的, 竞态, 谁跑的快就 resolve 谁, 或者 reject 谁, 其他的就陪跑

注意事项

  1. try catch 只能捕获到同步抛出的错误, 不能捕获到异步任务抛出的错误, promise 的错误也不能捕获到, 除非使用了 async await

  2. 多个.then 链式调用, 最后用一个 .catch 捕获到的错误, 怎么区分是哪个 .then 抛出的错误, 所以其实也抛出一个问题, 如下写法其实是不严谨的, 像这个 catch 其实会捕获到两个 promise 抛出的错误, 所以如果需要区分的话, 可以在每一个 Promise 都 catch 一下.

  3. 每一个 Promise 的状态只取决于上次的 Promise 返回的状态, 如果上一个 Promise return 了一个值, 不是异常, 那 reject 就会终止掉

参考文献 & 待看文章

Promise – MDN
Using_promises – MDN
Promises/A+规范
Promise.allSettled() – MDN
装饰器 – JELLY
queueMicrotask – MDN
执行上下文 – MDN
协作异步 JavaScript – MDN
Promise 面试题 – 倔金
JavaScript 执行机制 – 倔金
Node 事件循环机制 – Node

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享