HOW - 实现 Promise

一、介绍

实现一个简化的 Promise,包括基本的 then、catch、resolve、reject。

二、具体实现

class MyPromise {
  constructor(executor) {
  	console.log('MyPromise constructor...')
    this.state = 'pending'; // pending, fulfilled, rejected
    this.value = undefined;
    this.reason = undefined;
    this.onResolvedCallbacks = [];
    this.onRejectedCallbacks = [];

    const resolve = (value) => {
      if (this.state === 'pending') {
        this.state = 'fulfilled';
        this.value = value;
        this.onResolvedCallbacks.forEach(fn => fn());
      }
    };

    const reject = (reason) => {
      if (this.state === 'pending') {
        this.state = 'rejected';
        this.reason = reason;
        this.onRejectedCallbacks.forEach(fn => fn());
      }
    };

    try {
      executor(resolve, reject);
    } catch (err) {
      reject(err);
    }
  }
  then(onFulfilled, onRejected) {
    console.log('then...');
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
    onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason };

    const promise2 = new MyPromise((resolve, reject) => {
      console.log('this.state: ', this.state);
      if (this.state === 'fulfilled') {
        setTimeout(() => {
          try {
            const x = onFulfilled(this.value); // this.value 为 上一个 promise resolve(value)
            this.resolvePromise(promise2, x, resolve, reject);
          } catch (err) {
            reject(err);
          }
        }, 0);
      }

      if (this.state === 'rejected') {
        setTimeout(() => {
          try {
            const x = onRejected(this.reason);
            this.resolvePromise(promise2, x, resolve, reject);
          } catch (err) {
            reject(err);
          }
        }, 0);
      }

      if (this.state === 'pending') {
        this.onResolvedCallbacks.push(() => {
          setTimeout(() => {
            try {
              const x = onFulfilled(this.value);
              this.resolvePromise(promise2, x, resolve, reject);
            } catch (err) {
              reject(err);
            }
          }, 0);
        });
        this.onRejectedCallbacks.push(() => {
          setTimeout(() => {
            try {
              const x = onRejected(this.reason);
              this.resolvePromise(promise2, x, resolve, reject);
            } catch (err) {
              reject(err);
            }
          }, 0);
        });
      }
    });

    return promise2;
  }
  resolvePromise(promise2, x, resolve, reject) {
  	console.log('resolvePromise...')
    if (promise2 === x) {
      return reject(new TypeError('Chaining cycle detected for promise'));
    }

    let called = false;

    if (x instanceof MyPromise) {
      x.then(
        value => {
          if (called) return;
          called = true;
          this.resolvePromise(promise2, value, resolve, reject);
        },
        reason => {
          if (called) return;
          called = true;
          reject(reason);
        }
      );
    } else {
      resolve(x);
    }
  }
  catch(onRejected) {
    return this.then(null, onRejected);
  }

  static resolve(value) {
    return new MyPromise((resolve) => {
      resolve(value);
    });
  }

  static reject(reason) {
    return new MyPromise((_, reject) => {
      reject(reason);
    });
  }
}

这是一个简单的 MyPromise 实现,它模拟了 JavaScript 中的 Promise 对象。让我解释一下它的主要部分:

  1. 构造函数:

    • 构造函数接受一个执行器函数 executor,该函数有两个参数 resolvereject,它们分别用于将 Promise 状态从 pending 改变为 fulfilled 或 rejected。
    • 在构造函数中,我们初始化了 Promise 的状态为 pending,以及定义了 valuereason 属性来保存解决或拒绝时的值或原因。
    • onResolvedCallbacksonRejectedCallbacks 分别用于存储通过 then 方法注册的解决或拒绝处理函数。
  2. then 方法:

    • then 方法用于注册解决或拒绝时的处理函数,并返回一个新的 Promise 对象。
    • 如果当前 Promise 的状态已经是 fulfilledrejected,则通过 setTimeout 来确保异步执行处理函数,以保持 Promise 的异步特性。
    • 如果当前 Promise 的状态仍然是 pending,则将处理函数推入对应的回调数组中,以便在状态改变时执行。
  3. resolvePromise 方法:

    • resolvePromise 方法用于处理 then 方法返回的新 Promise 对象的状态。
    • 它检查处理函数的返回值 x,如果是一个 Promise,则递归处理其状态,直至解析出最终的值。
    • 如果 x 是一个普通值,则直接将其传递给新 Promise 的 resolve 方法。
  4. catch 方法:

    • catch 方法用于注册拒绝时的处理函数,实际上是 then 方法的语法糖,相当于 then(null, onRejected)
  5. 静态方法:

    • Promise.resolvePromise.reject 分别返回一个已解决或已拒绝的 Promise 对象。
    • Promise.all 接受一个 Promise 数组,并返回一个新的 Promise 对象,当数组中所有 Promise 都解决时,新 Promise 才解决,其值是一个包含所有解决值的数组。
    • Promise.race 接受一个 Promise 数组,并返回一个新的 Promise 对象,只要数组中有一个 Promise 解决或拒绝,新 Promise 即解决或拒绝。

这就是这个简单 MyPromise 实现的核心部分。它提供了 Promise 对象的基本功能,但并不包含所有的特性,比如异步任务的调度、微任务队列等。

三、示例:重点学习

注意如下几种示例的区别,并通过具体实现分析内部执行顺序。

示例 1

const p = new MyPromise((resolve, reject) => {
   resolve('123');
});
p.then(msg => {
  console.log('[result]', msg);
})

执行结果:
请添加图片描述
分析:

  1. 第一个 MyPromise constructor 是 const p = new MyPromise 执行的,第二个是 p.then
  2. 执行到 then 时,this.state === 'fulfilled',具体代码:
if (this.state === 'fulfilled') {
  setTimeout(() => {
    try {
      const x = onFulfilled(this.value); // this.value 为 上一个 promise resolve(value)
      this.resolvePromise(promise2, x, resolve, reject);
    } catch (err) {
      reject(err);
    }
  }, 0);
}

会先执行 onFulfilled,即 p.then(msg => { console.log('[result]', msg); }),把 123 打印出来,最后再执行 this.resolvePromise,具体代码:

  resolvePromise(promise2, x, resolve, reject) {
  	console.log('resolvePromise...')
    if (promise2 === x) {
      return reject(new TypeError('Chaining cycle detected for promise'));
    }
    if (x instanceof MyPromise) {
		//...
    } else {
      resolve(x);
    }
  }

即通过 resolve(x) 最后将 then 创建的第二个 promise resolve 状态从 pending 变为 fulfilled。

示例 2

const p = new MyPromise((resolve, reject) => {
   reject('123');
});
p.then(msg => {
  console.log('[result]', msg);
}, err => {
  console.log('[reason]', err);
})

执行结果:

请添加图片描述

分析:

  1. 初始 promise 内部调用 reject('123'),将执行如下逻辑:
if (this.state === 'rejected') {
  setTimeout(() => {
    try {
      const x = onRejected(this.reason);
      this.resolvePromise(promise2, x, resolve, reject);
    } catch (err) {
      reject(err);
    }
  }, 0);
}
  1. 同理,最后会执行 this.resolvePromise,将 then 创建的第二个 promise resolve 状态从 pending 变为 fulfilled。

示例 3

我们知道 promise 实例除了支持 then,还支持 catch,如果同时使用?

const p = new MyPromise((resolve, reject) => {
   reject('123');
});
p.then(msg => {
  console.log('[result]', msg);
}, err => {
  console.log('[reason1]', err);
})
p.catch(err => {
  console.log('[reason2]', err);
})

执行结果:
请添加图片描述

分析:

  1. 通过上述具体实现知道,catch 本质上会调用 return this.then(null, onRejected);,因此在执行结果可以看到执行了两次 then
  2. 并且 then 里的 onRejected 和 catch 里的 onRejected 可以独立执行不影响

示例 4

什么情况下会执行到 then 里的:

if (this.state === 'pending') {}

示例代码如下:

const p = new MyPromise((resolve, reject) => {
  setTimeout(() => {
    resolve('123');
  }, 2000);
});
p.then(msg => {
  console.log('[result]', msg);
})

执行结果:
请添加图片描述

分析:

  1. 可以发现 this.state === pending,这是因为初始时的 promise 里是一个 setTimeout 异步逻辑,因此执行到 then 时会发现此时处于 pending
  2. 这种情况具体实现:
if (this.state === 'pending') {
  this.onResolvedCallbacks.push(() => {
    setTimeout(() => {
      try {
        const x = onFulfilled(this.value);
        this.resolvePromise(promise2, x, resolve, reject);
      } catch (err) {
        reject(err);
      }
    }, 0);
  });
  this.onRejectedCallbacks.push(() => {
    setTimeout(() => {
      try {
        const x = onRejected(this.reason);
        this.resolvePromise(promise2, x, resolve, reject);
      } catch (err) {
        reject(err);
      }
    }, 0);
  });
}

可以发现会先如果当前 Promise 的状态仍然是 pending,则将处理函数推入对应的回调数组中,以便在状态改变时执行。

  1. 所以当 setTimeout 计时结束可以执行回调时,会执行:
const resolve = (value) => {
  if (this.state === 'pending') {
    this.state = 'fulfilled';
    this.value = value;
    this.onResolvedCallbacks.forEach(fn => fn());
  }
};

首先会变更状态 pending 为 fulfilled,然后从回调数组中取出之前推入的处理函数进行执行,从而输出 123

示例 5

我们都知道链式 then 调用,那究竟底层会发生什么?

示例代码如下:

const p = new MyPromise((resolve, reject) => {
  setTimeout(() => {
    resolve('123');
  }, 2000);
});
p.then(msg => {
  console.log('[result]', msg);
  return '456';
}).then(msg => {
  console.log('[result]', msg);
})

执行结果:
请添加图片描述

  1. 可以发现有两次then,并且创建了两个新 promise 实例
  2. 会依次执行 then,并且在上一个 promise resolve 之后将 this.value 作为结果打印

示例 6

前面我们的 then 函数要么不做返回,要么返回一个简单的字符串。那假如我们在 then 里返回的是一个 promise 呢?

const p = new MyPromise((resolve, reject) => {
  setTimeout(() => {
    resolve('123');
  }, 2000);
});
p.then(msg => {
  console.log('[result]', msg);
  return new MyPromise((resolve, reject) => {
    resolve('456');
  })
}).then(msg => {
  console.log('[result]', msg);
})

执行结果:
请添加图片描述
分析:

  1. 最开始两个 then...p.then().then() 执行得到的
  2. 当初始 promise 内的 setTimeout 计时结束执行回调时,成功将初始 promise 状态从 pending 变为 fulfilled,并且取出回调函数列表中推入的如下内容:
p.then(msg => {
  console.log('[result]', msg);
  return new MyPromise((resolve, reject) => {
    resolve('456');
  })
})

执行后会先打印 [result] 123,接着打印 MyPromise constructor...,即 return new MyPromise() 构造的。

  1. 紧接着执行了 this.resolvePromise,注意这里 x instance MyPromise 将为真,具体代码如下:
resolvePromise(promise2, x, resolve, reject) {
	console.log('resolvePromise...')
  if (promise2 === x) {
    return reject(new TypeError('Chaining cycle detected for promise'));
  }

  let called = false;
  if (x instanceof MyPromise) {
    x.then(
      value => {
        if (called) return;
        called = true;
        this.resolvePromise(promise2, value, resolve, reject);
      },
      reason => {
        if (called) return;
        called = true;
        reject(reason);
      }
    );
  } else {
    //...
  }
}

可以从上述代码看出,对于 return 的新 promise,会执行其 then,所以在截图中打印了第三个 then...。只不过要注意, resolve 在 this.resolvePromise 是透传的,因此当内部 resolve 之后,第二个 then 能拿到 456 并打印成功。

示例 7

自己分析一下下面会如何执行吧。

const p = new MyPromise((resolve, reject) => {
  setTimeout(() => {
    resolve('123');
  }, 2000);
});
p.then(msg => {
  console.log('[result]', msg);
  return new MyPromise((resolve, reject) => {
    setTimeout(() => {
      resolve('456');
    }, 4000);
  })
}).then(msg => {
  console.log('[result]', msg);
})
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值