您现在的位置是:网站首页> 编程资料编程资料

JavaScript Promise原理与实现刨析_javascript技巧_

2023-05-24 242人已围观

简介 JavaScript Promise原理与实现刨析_javascript技巧_

1 Promise核心逻辑实现

Promise对象是一个原生的javascript对象,是一种异步编程的解决方案,它通过一个回调,可以避免更多的回调,接下来说明其原理及实现。

下面一段代码是Promise的基本使用:

new Promise((resolve, reject) => { resolve("成功"); reject("失败"); }) Promise.then(value => { }, reason => { }) 

从上面的代码中,我们可以分析出一些关键点:

  • Promise创建时需要使用new关键字,那么我们可以知道Promise就是一个类;
  • 在执行这个类的时候,需要传递一个执行器进去,这个执行器会立即执行;
  • 在执行器中有两个参数,resolvereject,它们都是函数,用来改变Promise中的状态;
  • Promise中有三种状态,分别是:成功fulfilled、失败rejected和等待pending,状态只能从pending—>fulfilled,或者pending—>rejected,状态一旦确定就不可以更改;
  • resolvereject函数是用来更改状态的,其中,resolve将状态更改为fulfilledreject将状态更改为rejected
  • then方法接收两个函数作为参数,它需要判断状态,如果成功调用第一个回调函数,如果失败调用第二个回调函数,并且then方法是被定义在原型对象中的,第一个回调函数的参数为成功之后的值,第二个回调函数的参数为失败之后的原因;

接下来我们根据上面分析出的内容,一步一步实现我们自己的Promise。

首先创建一个类,为constructor构造函数传入一个执行器,因为执行器需要立即执行,因此在构造函数中调用该执行器。

class MyPromise { constructor(executor) { // 接收一个执行器 executor(); // 执行器会立即执行 } } 

在执行器中有两个参数resolvereject,它们都是函数,因此在类中创建两个箭头函数resolvereject,在执行器executor中使用this来调用它们。

为什么使用箭头函数:

注意,我们在Promise中调用resolvereject是直接调用的,如果将它们写成普通函数,那么会将this指向window或者undefined,如果我们写成箭头函数,那么它们的this就会指向类的实例对象。

class MyPromise { constructor(executor) { // 接收一个执行器 executor(this.resolve, this.reject); // 执行器会立即执行 } resolve = () => { } reject = () => { } } 

resolvereject这两个函数是用来改变状态的,因此我们将状态定义在类的外面,因为它们会被频繁使用到。在类中我们默认定义状态status是等待pending,当调用resolve时,状态改为成功,当调用reject时,状态改为失败。并且状态一旦确定不可更改,因此我们要在两个函数中判断当前状态是否为等待,不是则返回。

const PENDING = "pending"; // 等待 const FULFILLED = "fulfilled"; // 成功 const REJECTED = "rejected"; // 失败 class MyPromise { constructor(executor) { // 接收一个执行器 executor(this.resolve, this.reject); // 执行器会立即执行 } status = PENDING; // 状态默认为pending等待 resolve = () => { if (this.status !== PENDING) return; // 当状态不是等待,直接返回 this.status = FULFILLED; // 将状态改为成功 } reject = () => { if (this.status !== PENDING) return; // 当状态不是等待,直接返回 this.status = REJECTED; // 将状态改为失败 } }

Promise中的then方法有两个参数,当状态成功调用第一个,状态失败调用第二个,因此内部需要使用if来判断状态。调用成功或者失败函数时,需要为其传入参数,那么我们知道成功的值是由resolve传递来的,失败的原因是由reject传递来的,因此我们在Promise中声明两个属性存放两个值。

const PENDING = "pending"; // 等待 const FULFILLED = "fulfilled"; // 成功 const REJECTED = "rejected"; // 失败 class MyPromise { constructor(executor) { // 接收一个执行器 executor(this.resolve, this.reject); // 执行器会立即执行 } status = PENDING; // 状态默认为pending等待 value = undefined; // 成功之后的值 reason = undefined; // 失败之后的原因 resolve = value => { // value是成功之后的值 if (this.status !== PENDING) return; // 当状态不是等待,直接返回 this.status = FULFILLED; // 将状态改为成功 this.value = value; // 将成功的值传递 } reject = reason => { // reason是失败之后的原因 if (this.status !== PENDING) return; // 当状态不是等待,直接返回 this.status = REJECTED; // 将状态改为失败 this.reason = reason; // 将失败的值传递 } then(successCallback, failCallback) { // then方法有两个参数 if (this.status === FULFILLED) { // 成功调用第一个回调函数 successCallback(this.value); } else if (this.status === REJECTED) { // 失败调用第二个回调函数 failCallback(this.reason); } } }

到这里我们就实现了一个最简单的Promise了。

2 加入异步逻辑

上面我们实现的Promise,实际上并没有考虑异步情况,比如说下面的代码中,2秒后调用成功的回调,如果这时调用then方法,那么当前的状态是等待pending,但是我们并没有判断状态是pending时的情况。

new Promise((resolve, reject) => { setTimeout(() => { resolve("成功"); }, 2000); }) Promise.then(value => { }, reason => { }) 

因此在then方法中,我们应该判断当状态是等待的情况。当状态是等待时,我们没有办法调用成功或者失败的回调,这时我们需要将成功回调和失败回调储存起来。

const PENDING = "pending"; // 等待 const FULFILLED = "fulfilled"; // 成功 const REJECTED = "rejected"; // 失败 class MyPromise { constructor(executor) { // 接收一个执行器 executor(this.resolve, this.reject); // 执行器会立即执行 } status = PENDING; // 状态默认为pending等待 value = undefined; // 成功之后的值 reason = undefined; // 失败之后的原因 successCallback = undefined; // 成功的回调函数 failCallback = undefined; // 失败的回调函数 resolve = value => { // value是成功之后的值 if (this.status !== PENDING) return; // 当状态不是等待,直接返回 this.status = FULFILLED; // 将状态改为成功 this.value = value; // 将成功的值传递 } reject = reason => { // reason是失败之后的原因 if (this.status !== PENDING) return; // 当状态不是等待,直接返回 this.status = REJECTED; // 将状态改为失败 this.reason = reason; // 将失败的值传递 } then(successCallback, failCallback) { // then方法有两个参数 if (this.status === FULFILLED) { // 成功调用第一个回调函数 successCallback(this.value); } else if (this.status === REJECTED) { // 失败调用第二个回调函数 failCallback(this.reason); } else { // 当状态为等待时,将成功回调和失败回调存储起来 this.successCallback = successCallback; this.failCallback = failCallback; } } }

将成功回调和失败回调存储起来之后,我们则要在resolvereject方法中判断是否存在成功或者失败的回调,如果存在,则将其调用。

const PENDING = "pending"; // 等待 const FULFILLED = "fulfilled"; // 成功 const REJECTED = "rejected"; // 失败 class MyPromise { constructor(executor) { // 接收一个执行器 executor(this.resolve, this.reject); // 执行器会立即执行 } status = PENDING; // 状态默认为pending等待 value = undefined; // 成功之后的值 reason = undefined; // 失败之后的原因 successCallback = undefined; // 成功的回调函数 failCallback = undefined; // 失败的回调函数 resolve = value => { // value是成功之后的值 if (this.status !== PENDING) return; // 当状态不是等待,直接返回 this.status = FULFILLED; // 将状态改为成功 this.value = value; // 将成功的值传递 this.successCallback && this.successCallback(this.value); // 如果成功回调存在,则调用 } reject = reason => { // reason是失败之后的原因 if (this.status !== PENDING) return; // 当状态不是等待,直接返回 this.status = REJECTED; // 将状态改为失败 this.reason = reason; // 将失败的值传递 this.failCallback && this.failCallback(this.reason); // 如果失败回调存在,则调用 } then(successCallback, failCallback) { // then方法有两个参数 if (this.status === FULFILLED) { // 成功调用第一个回调函数 successCallback(this.value); } else if (this.status === REJECTED) { // 失败调用第二个回调函数 failCallback(this.reason); } else { // 当状态为等待时,将成功回调和失败回调存储起来 this.successCallback = successCallback; this.failCallback = failCallback; } } }

这是我们就处理了异步的情况了。

3 then方法添加多次调用逻辑

Promise的then方法可以调用多次,我们接着处理这部分。

let promise = new Promise((resolve, reject) => { }) promise.then(value => { }) promise.then(value => { }) promise.then(value => { })

如果多次调用了then方法,就需要考虑两种情况:同步情况和异步情况。如果是同步情况,那么直接就可以调用回调函数,我们已经不需要多做处理了,如果是异步情况,那么我们需要将每一个回调函数储存起来。

我们之前在then方法中判断等待的时候,也将成功和失败的回调存储起来,但是每次只能存储一个,因此我们需要将存储的容器设为数组,通过数组的push方法将回调函数存储起来。

const PENDING = "pending"; // 等待 const FULFILLED = "fulfilled"; // 成功 const REJECTED = "rejected"; // 失败 class MyPromise { constructor(executor) { // 接收一个执行器 executor(this.resolve, this.reject); // 执行器会立即执行 } status = PENDING; // 状态默认为pending等待 value = undefined; // 成功之后的值 reason = undefined; // 失败之后的原因 successCallback = []; // 使用数组存储成功的回调函数 failCallback = []; // 使用数组存储失败的回调函数 resolve = value => { // value是成功之后的值 if (this.status !== PENDING) return; // 当状态不是等待,直接返回 this.status = FULFILLED; // 将状态改为成功 this.value = value; // 将成功的值传递 this.successCallback && this.successCallback(this.value); // 如果成功回调存在,则调用 } reject = reason => { // reason是失败之后的原因 if (this.status !== PENDING) return; // 当状态不是等待,直接返回 this.status = REJECTED; // 将状态改为失败 this.reason = reason; // 将失败的值传递 this.failCallback && this.failCallback(this.reason); // 如果失败回调存在,则调用 } then(successCallback, failCallback) { // then方法有两个参数 if (this.status === FULFILLED) { // 成功调用第一个回调函数 successCallback(this.value); } else if (this.status === REJECTED) { // 失败调用第二个回调函数 failCallback(this.reason); } else { // 当状态为等待时,将成功回调和失败回调存储起来 this.successCallback.push(successCallback); this.failCallback.push(failCallback); } } }

更改成数组之后,那么我们原来在resolvereject函数中调用成功或者失败的回调函数就不可以使用了,而是在其中循环调用数组中的回调函数。

const PENDING = "pending"; // 等待 const FULFILLED = "fulfilled"; // 成功 const REJECTED = "rejected"; // 失败 class MyPromise { constructor(executor) { // 接收一个执行器 executor(this.resolve, this.reject); // 执行器会立即执行 } status = PENDING; // 状态默认为pending等待 value = undefined; // 成功之后的值 reason = undefined; // 失败之后的原因 successCallback = []; // 成功的回调函数 failCallback = []; // 失败的回调函数 resolve = value => { // value是成功之后的值 if (this.status !== PENDING) return; // 当状态不是等待,直接返回 this.status = FULFILLED; // 将状态改为成功 this.value = value; // 将成功的值传递 while (this.successCallback.length) { // 循环执行数组中的回调函数 this.successCallback.shift()(this.value); // 调用回调函数 } } reject = reason => { // reason是失败之后的原因 if (this.status !==
                
                

-六神源码网