Promise

168 阅读5分钟

Promise

promise 的参数和状态

Promise 是一个构造函数,参数是一个可执行函数,默认会同步执行 可执行函数有两个参数 resolve, reject。promise 有三个状态 pending, fulfill, rejected.

promise 实例有个 then 方法, then 有两个函数参数, 如果成功则执行成功的函数并传入成功的 value, 如果失败则执行失败的函数并将失败的 reason 传入。

const PENDING = "PENDING";
const FULFILLED = "FULFILLED";
const REJECTED = "REJECTED";

class Promise {
  constructor(executor) {
    this.status = PENDING;
    // 用户调用 resolve 和 reject 可以将对应的结果暴露在当前的 promise 实例上
    this.value = undefined// 实例上需要使用
    this.reason = undefined;

    const resolve = (value) => {
      this.value = value;
      this.status = FULFILLED;
    };

    const reject = (reason) => {
      this.reason = reason;
      this.status = REJECTED;
    };

    executor(resolve, reject); // 默认 new Promise 中的函数会立即执行
  }

  then(onFulfilled, onRejected) {
    if (this.status === FULFILLED) {
      onFulfilled(this.value);
    }

    if (this.status === REJECTED) {
      onRejected(this.reason);
    }
  }
}

try catch peomise 的执行函数

executor 执行的过程可能会报错,需要 try catch 一下,报错后会走 reject 函数

try {
  executor(resolve, reject); // 默认 new Promise 中的函数会立即执行catch (error) {
  reject(error);
}

promise 状态切换

  • 如果用户调了 resolve, promise 的 status 会变成 fulfilled, 并且会把成功的 value 保存起来

  • 如果用户调了 reject, promise 的 status 会变成 rejected, 并且会把失败的 reason 保存起来

  • promise 的状态只能从 pending 变成 fulfilled 或 rejected, 不能从 fulfilled 变成 rejected 或 从 rejected 变成 fulfilled

const resolve = (value) => {
  if (this.status === PENDING) {
    // // 不能 从失败变成成功,只能从 pending 变成成功
    this.value = value;
    this.status = FULFILLED;
  }
};

const reject = (reason) => {
  console.log(this.status === PENDING);
  if (this.status === PENDING) {
    // 不能从成功变成失败,只能从 pending 变成失败
    this.reason = reason;
    this.status = REJECTED;
  }
};

异步执行 resolve 和 reject

如果 resolve 或 reject 是在异步中执行的, 执行 then 方法的时候 promise status 是 pending 就不会执行成功或失败的回调了。需要在执行 then 的时候先存起来,等执行 resolve 或 reject 的时候再执行。(发布订阅模式)

异步执行 resolve 或 reject

new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve(1)
    })
})

此时 then 方法执行时,还没有执行 resolve, 如果不做处理后面 resolve 执行就拿不到返回值了。

const PENDING = "PENDING";
const FULFILLED = "FULFILLED";
const REJECTED = "REJECTED";
class Promise {
  constructor(executor) {
    this.status = PENDING;
    this.value = undefined// 实例上需要使用
    this.reason = undefined;
    this.onResolvedCallbacks = [];
    this.onRejectedCallbacks = [];

    const resolve = (value) => {
      if (this.status === PENDING) {
        // // 不能 从失败变成成功,只能从 pending 变成成功
        this.value = value;
        this.status = FULFILLED;
        this.onResolvedCallbacks.forEach((fn) => fn());
      }
    };

    const reject = (reason) => {
      console.log(this.status === PENDING);
      if (this.status === PENDING) {
        // 不能从成功变成失败,只能从 pending 变成失败
        this.reason = reason;
        this.status = REJECTED;
        this.onRejectedCallbacks.forEach((fn) => fn());
      }
    };

    try {
      executor(resolve, reject); // 默认 new Promise 中的函数会立即执行
    } catch (error) {
      reject(error);
    }
  }

  then(onFulfilled, onRejected) {
    if (this.status === FULFILLED) {
      onFulfilled(this.value);
    }

    if (this.status === REJECTED) {
      onRejected(this.reason);
    }

    if (this.status === PENDING) {
      // 先把 then的回调存起来, 发布订阅模式
      console.log("PENDING");
      this.onResolvedCallbacks.push(() => {
        onFulfilled(this.value);
      });
      this.onRejectedCallbacks.push(() => {
        onRejected(this.reason);
      });
    }
  }
}

then 方法可以链式调用

可以在 then 方法(成功和失败)中返回一个 promise, promise 会采用返回的 promise 的成功的值或失败的原因, 传递到外层下一层 then 中.

  1. then 方法中成功的回调或者失败的回调返回的是一个 promise , 那么会采用返回的 promise 的状态, 走外层下一次 then 中的成功或失败, 同时将 promise 处理后的结果向下传递
  2. then 方法中成功的回调或者失败的回调返回的是一个普通值(不是 promise) , 那么会将返回的结果传递到下一次 then 的成功回调中去
  3. then 方法中成功的回调或者失败的回调 执行时出错返回失败的 promise 会走到外层下一个 then 中的失败中去
readFile("./a.txt""utf8")
  .then(
    (data) => {
      return readFile(data, "utf8");
    },
    (err) => {
      console.log("fail", err);
    }
  )
  .then(
    () => {
      console.log(data);
    },
    (err) => {
      console.log("fail2", err);
    }
  );

实现 promise 的链式调用

如果是通过 return this 来实现,是不行的, 因为 promise 的状态只能从 pending 变成 fulfilled 或 rejected。 return this 的话, 后面的 then 方法的状态没办法改变了。

通过 return new Promise() 每次产生一个全新的 promise, 来保证状态可以正常的切换。

 then(onFulfilled, onRejected) {
    // 每次调用 then 方法, 都返回一个全新的 promise
    const promise2 = new Promise((resolve, reject) => {
    // x 就是上一个 then 成功或失败的返回值, 这个 x 决定 promise2 走成功还是走失败
      if(this.status === FULFILLED) {
        
          try {
            const x = onFulfilled(this.value)
             resolve(x)
          } catch (error) {
            reject(error)
          }
      }

      if(this.status === REJECTED) {
          try {
            const x = onRejected(this.reason)
             resolve(x)
          } catch (error) {
            reject(error)
          }
      }

      if (this.status === PENDING) { // 先把 then的回调存起来, 发布订阅模式
        // console.log('PENDING');
        this.onResolvedCallbacks.push(() => {
            try {
              const x = onFulfilled(this.value)
               resolve(x)
            } catch (error) {
              reject(error)
            }
        })
        this.onRejectedCallbacks.push(() => {
            try {
              const x = onRejected(this.reason)
               resolve(x)
            } catch (error) {
              reject(error)
            }
        })
      }
    })
    return promise2
  }

x 的值可能又是一个 promise, 需要判断 x 的值再进一步处理。通过 resolvePromise 统一处理

resolvePromise(x, promise2, resolve, reject) 替换掉 resolve(x); 

这里用到的了 promise2, 但是 promise2 是通过 new Promise 的过程中获取的。直接这么用的话就报错了。我们可以在下一个事件循环中使用,这里使用了 queueMicrotask

queueMicrotask(() => {
    try {
      const x = onRejected(this.reason)
      resolvePromise(x, promise2, resolve, reject) 
    } catch (error) {
      reject(error)
    }
})

resolvePromise 的实现

resolvePromise 的功能主要是判断 x 的值及做出相应的处理

function resolveromise(x, promise2, resolve, reject) {
  if (x === promise2) {
    // 问题1
    return reject(new Error("循环引用"));
  }
  // 继续判断 x 是不是一个 promise,  promise 需要有 then 方法(啥时候是函数, 别人写的 promise 就有可能是函数)
  if ((typeof x === "object" && x !== null) || typeof x === "function") {
    // 这种情况下才有可能是 promise
    // 继续判断 x 是否有 then
    // let then = x.then
    let called = false; // 问题3
    try {
      let then = x.then; // 尝试取 then 的方法  {then: 1} // 这种情况 then 不是一个方法
      if (typeof then === "function") {
        // x.then// x.then 是一个函数, 就认为 x 是一个 promise 了
        // x.then() // 这个会再次取一次属性,触发 get 方法
        // then.call(x) // 这个不会
        // 问题2
        then.call(
          x,
          (y) => {
            // y 可能还是一个 promise, 所以要再次进行解析流程
            if (called) return; // 防止多次调用成功或失败
            called = true;
            resolvePromise(y, promise2, resolve, reject); // 这里的 promise2 应该是 x 吧
            // resolve(y)
          },
          (r) => {
            if (called) return; // 防止多次调用成功或失败
            called = true;
            reject(r);
          }
        );
      } else {
        // x = {then: 1}
        resolve(x);
      }
    } catch (error) {
      if (called) return; // 防止多次调用成功或失败
      called = true;
      reject(error); // 让 promise2 变成失败态
    }
  } else {
    // 如果不是 promise, fulfill the promise with x
    // x 是普通值
    resolve(x);
  }
}

问题 1

const promise2 = new Promise((resolve, reject) => {
  resolve();
}).then((data) => {
  return promise2;
});

问题 2

避免多次取值报错

let time = 0;
Object.defineProperty({}, "then", {
  get() {
    if (time++ === 2) {
      return new Error("error");
    }
  },
});

问题3

多次调用 resolve 或 reject, 只有第一次生效

new Promise((resolve, reject) => {
    resolve(1)
    resolve(2)
})

then 透传值

new Promise((resolve, reject)=> {
    resolve(1)
}).then().then().then(data => {
    console.log(data) // 1
})

当 then 方法调用不传参数时,接收到的值是可以透传到下一个 then 的。 需要为 then 的参数添加一个默认值兜底。

onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : v => v
onRejected = typeof onRejected === 'function' ? onRejected : e => { throw e}