likes
comments
collection
share

手写一个promiseA+,附上代码,讲解,以及promise基础方法等

作者站长头像
站长
· 阅读数 87

本文包含--为什么有Promise? promise基础、promise实例、方法讲解,以及如何手写一个基础版本的Promise,进阶版本的PromiseA+版本,文章针对难懂的知识点会进行代码+文字结合讲解。原创不易,如果觉得还不错,记得点赞收藏!文章很长,取自己需要即可~

一、为什么会有promise的出现?

Promise 是为了解决 JavaScript 中回调地狱问题而引入的。在复杂的程序中,实现回调函数变得非常困难。在某些情况下,开发人员不得不在回调函数内部实现另一个回调函数,导致回调地狱。为了解决这个问题,引入了 Promise,它有助于在 JavaScript 中高效地编写复杂的代码.

二、 promise基础

Promise 对象用于表示一个异步操作的最终完成(或失败)及其结果值。

1、三种状态

  • 待定(pending) :初始状态,既没有被兑现,也没有被拒绝。
  • 已兑现(fulfilled) :操作成功完成。
  • 已拒绝(rejected) :操作失败。

2、不可逆性

一旦触发就不能再更改,比如状态由pending变为fulfilled,即不可以再变为rejected。

3、链式调用

then 方法返回一个新的 promise 实例,这是实现链式调用的根本,promise的链式调用是指多次调用then方法,将多个promnise链接在一起,每次调用then方法都会返回一个新的promise,其中每一个promise都依赖前一个promise的结果,当第一个promise成功或者拒绝时,就会触发第二个promise的成功或者拒绝,

举个例子:

function doubleAfter2Seconds(x) {
  return new Promise(resolve => {
    setTimeout(() => {
      resolve(x * 2);
    }, 2000);
  });
}

doubleAfter2Seconds(10)
  .then(result => doubleAfter2Seconds(result))
  .then(result => doubleAfter2Seconds(result))
  .then(result => console.log(result));

在这个例子中,定义了一个函数doubleAfter2Seconds,它接受一个参数x并返回一个promise。该promise在2秒后解决,并将结果设置为x * 2

然后,我们调用该函数并传入值10。这将返回一个promise,该promise在2秒后解决并返回值20

接下来,我们调用then方法并传入一个函数,该函数接受上一个promise的结果(即20)作为参数,并再次调用doubleAfter2Seconds函数。这将返回一个新的promise,该promise在2秒后解决并返回值40

然后,我们再次调用then方法并传入一个函数,该函数接受上一个promise的结果(即40)作为参数,并再次调用doubleAfter2Seconds函数。这将返回一个新的promise,该promise在2秒后解决并返回值80

最后,我们再次调用then方法并传入一个函数,该函数接受上一个promise的结果(即80)作为参数,并使用console.log打印它。

因此,在运行此代码后,将在6秒后看到输出为80。

4、构造函数

Promise 是一个构造函数,它的构造函数接收一个函数为参数,并且传入两个参数:resolve,reject,分别表示异步操作执行成功后的回调函数和异步操作执行失败后的回调函数。

举个例子:

new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve()
    }, 1000)
}).then(res => {}, err => {})

创建了一个 Promise 对象,并将要执行的异步函数传入到 Promise 的参数中执行。在异步执行结束后调用 resolve() 函数,就可以在 Promise 的 then 方法中获取到异步函数的执行结果。

三、 promise实例以及方法讲解

1、静态方法

该方法接收一个 promise 的 iterable 类型(注:Array,Map,Set 都属于 ES6 的 iterable 类型)的输入,并且只返回一个 Promise 实例,

当所有传入的 Promise 对象都变为完成状态,或者传入的可迭代对象内没有 Promise 时,Promise.all() 返回的 Promise 对象会异步地变为完成状态。此时,返回的 Promise 对象的结果是一个数组,它包含所有传入的可迭代参数对象的值(也包括非 Promise 值)。

如果传入的 Promise 对象中有一个失败(rejected),Promise.all() 返回的 Promise 对象会异步地将失败的那个结果给失败状态的回调函数,而不管其他 Promise 是否完成。reject的是第一个抛出的错误信息

下面是一个简单的例子:

var p1 = Promise.resolve63);
var p2 = 2345;
var p3 = new Promise((resolve, reject) => {
    setTimeout(resolve, 100, 'foo');
});

Promise.all([p1, p2, p3]).then(values => {
    console.log(values); // [6, 2345, "foo"]
});

在这个例子中,我们使用 Promise.all() 等待所有都完成(或第一个失败)。如果参数中包含非 promise 值,这些值将被忽略,但仍然会被放在返回数组中(如果 promise 完成的话)。

该方法接收一个由 Promise 所组成的可迭代对象,该方法会返回一个新的 promise。

一旦可迭代对象内的任意一个 promise 变成了兑现状态,那么由该方法所返回的 promise 就会变成兑现状态,并且它的兑现值就是首先兑现的 promise 的兑现值。

如果可迭代对象内的 promise 最终都没有兑现(即所有 promise 都被拒绝了),那么该方法所返回的 promise 就会变成拒绝状态,并且它的拒因会是一个 AggregateError 实例,这是 Error 的子类,用于把单一的错误集合在一起。

下面是一个简单的例子:

const pErr = new Promise((resolve, reject) => {
    reject('总是失败');
});

Promise.any([pErr]).catch((err) => {
    console.log(err);
})

在这个例子中,我们使用 Promise.any() 来获取任何第一个完成的 promise 的值。如果可迭代对象内所有的 promises 都被拒绝了,那么该方法所返回的 promise 就会异步地切换至被拒状态,并用一个 AggregateError (继承自 Error )实例来作为它的拒因。

竞赛:顾名思义,该方法接收一个包含多个 Promise 对象的可迭代对象,并返回一个新的 Promise 对象。该 Promise 对象在多个 Promise 中任意一个 Promise 对象状态变为 fulfilled 或 rejected 时立即返回该 Promise 对象的值或原因。

举个例子:

var p1 = new Promise(function(resolve, reject) {
    setTimeout(resolve, 500, 'one');
});
var p2 = new Promise(function(resolve, reject) {
    setTimeout(resolve, 100, 'two');
});

Promise.race([p1, p2]).then(function(value) {
    console.log(value); // "two"
});

在这个例子中,我们使用 Promise.race() 来获取多个 Promise 中任意一个 Promise 对象状态变为 fulfilled 或 rejected 时立即返回该 Promise 对象的值或原因。·p2的状态更快地变为 fulfilled,所以Promise.race()返回的值是p2 的值。

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

举个例子

  1. 所有 promise完成

下面定义了一个 promise 数组 handlePromise ,所有的 promise 都能够成功的 resolve 值,如下:

const color = ["red", "blue"];
const cars = ["ad", "bc"];

const handlePromise = Promise.allSettled([
    resolveTimeout(color, 2000),
    resolveTimeout(cars, 1000),
]);

// 等待 2 秒 ...
const currentParams = async () => {
    try {
        const statuses = await handlePromise;
        console.log(statuses);
    } catch (error) {
        console.log(error);
    }
};

currentParams(); // [{ status: 'fulfilled', value: [ 'ad', 'bc' ] },{ status: 'fulfilled', value: [ 'red', 'blue' ] }]

从上面执行的结果来看 Promise.allSettled() 返回的一个 promiseresolve 状态数组是按照执行前 handlePromise 的顺序组成其结果。

  1. 一个 promise 的状态是 rejected

将上面第一个 promise 出现异常被 rejected ,如下:

const color = ["red", "blue"];

const handlePromise = Promise.allSettled([
    resolveTimeout(color, 2000),
    rejectTimeout(new Error("cars is empty"), 1000),
]);

// 等待 2 秒 ...
const currentParams = async () => {
    try {
        const statuses = await handlePromise;
        console.log(statuses);
    } catch (error) {
        console.log(error);
    }
};

currentParams(); // // [{ status: 'fulfilled', value: ['red', 'blue'] },{ status: 'rejected', reason: Error('cars is empty') }]

即使输入数组中的第二个 promiserejectedhandlePromise 仍然可以成功解析状态数组。

  1. 所有 promiserejected

将上面所有的 promise 出现异常被 rejected ,如下:


const handlePromise = Promise.allSettled([
    rejectTimeout(new Error("color is empty"), 2000),
    rejectTimeout(new Error("cars is empty"), 1000),
]);

// 等待 2 秒 ...
const currentParams = async () => {
    try {
        const status = await handlePromise;
        console.log(status);
    } catch (error) {
        console.log(error);
    }
};

currentParams(); // // [{ status: 'rejected', reason: Error('color is empty') },{ status: 'rejected', reason: Error('cars is empty') }]

简单概括:Promise.allSettled()可以用来执行并行和独立的异步操作并且需要收集所有结果。

举个例子:

Promise.reject(new Error('出错了')).catch(e => {
    console.log(e); // Error: 出错了
});

使用 Promise.reject() 来返回一个带有拒绝原因的 Promise 对象。在这个例子中,我们传入了一个 Error 对象作为拒绝原因。当调用 catch() 方法时,可以获取到这个拒绝原因。

Promise.resolve(value) 方法返回一个以给定值解析后的 Promise 对象。如果这个值是一个 Promise 对象,那么这个方法返回的 Promise 对象就是这个 Promise 对象;如果这个值是一个 thenable(即带有 then 方法的对象),那么返回的 Promise 对象会“跟随”这个 thenable 的对象,采用它的最终状态;否则返回的 Promise 对象会以此值为成功状态。

举个例子:

Promise.resolve('Success').then(function(value) {
    console.log(value); // "Success"
}, function(value) {
    // 不会被调用
});

在这个例子中,我们使用 Promise.resolve() 来返回一个以给定值解析后的 Promise 对象。在这个例子中,我们传入了一个字符串 'Success' 作为解析后的值。当我们调用 then() 方法时,可以获取到这个解析后的值。

当然可以。下面是另一个 Promise.resolve() 的例子:

let p = Promise.resolve([1,2,3]);
p.then(function(v) {
    console.log(v[0]); // 1
});

在这个例子中,我们使用 Promise.resolve() 来返回一个以给定值解析后的 Promise 对象。在这个例子中,我们传入了一个数组 [1,2,3] 作为解析后的值。当我们调用 then() 方法时,可以获取到这个解析后的值。

Promise.resolve('Success') 返回一个以 'Success' 值解析后的 Promise 对象。当我们调用 then() 方法时,第一个参数是一个函数,它会在 Promise 对象的状态变为 fulfilled 时被调用;第二个参数也是一个函数,它会在 Promise 对象的状态变为 rejected 时被调用。

在这个例子中,由于 Promise.resolve('Success') 返回的 Promise 对象的状态是 fulfilled,所以第一个参数中的函数会被调用,而第二个参数中的函数不会被调用。

2、实例方法

1、 Promise.prototype.catch()

该方法返回一个 Promise 对象,并且只处理拒绝的情况。它的行为与调用Promise.prototype.then(undefined, onRejected) 相同。

举个例子:

let p1 = new Promise((resolve, reject) => {
    reject('出错了');
});

p1.catch((reason) => {
    console.log(reason); // "出错了"
});

在这个例子中,我们创建了一个 Promise 对象 p1,并在其中调用了 reject() 方法来拒绝这个 Promise 对象。然后我们调用了 catch() 方法来处理这个拒绝的情况。catch() 方法中的函数会被调用,并且参数 reason 的值就是我们传入 reject() 方法中的值。

2、 Promise.prototype.then() 为 promise 添加被兑现和被拒绝状态的回调函数,其以回调函数的返回值兑现 promise。若不处理已兑现或者已拒绝状态(例如,onFulfilled 或 onRejected 不是一个函数),则返回 promise 被敲定时的值。

Promise.prototype.then() 是一个实例方法,
用于为 `Promise` 注册回调函数。它最多需要两个参数: Promise 的成功和失败情况的回调函数。

下面是一个简单的例子:

var p1 = new Promise((resolve, reject) => {
  resolve('成功!'); 
});

p1.then(value => {
  console.log(value); // 成功!
}, reason => {
  console.error(reason);
});

创建了一个新的 Promise 对象 p1,并在其内部调用了 resolve 函数来改变 Promise 的状态为成功。然后我们调用了 p1.then() 方法来注册回调函数,当 Promise 变成接受状态(fulfilled)时,第一个回调函数将被调用,并输出“成功!”。

  • Promise.prototype.finally() 为 promise 添加一个回调函数,并返回一个新的 promise。这个新的 promise 将在原 promise 被兑现时兑现。而传入的回调函数将在原 promise 被敲定(无论被兑现还是被拒绝)时被调用。

    Promise.prototype.finally() 是一个方法,它允许你在 Promise 实例被解决(无论是完成还是拒绝)时调度一个函数。它立即返回一个等效的 Promise 对象,允许你将其他 Promise 方法的调用链接起来。这样可以避免在 Promisethen()catch() 处理程序中重复代码¹。

下面是一个简单的例子,它演示了如何使用 finally() 来清理一些东西,无论 Promise 的结果如何:

let isLoading = true;

fetch(myRequest).then(function(response) {
    // 处理响应
}).catch(function(error) {
    // 处理错误
}).finally(function() {
    isLoading = false;
});

在这个例子中,无论 fetch() 请求成功还是失败,isLoading 都会被设置为 false

首先定义class类promiseComment, 内部包含构造器constructor

四、promise A+ 代码

class promiseComment {
    static FULLFILED = '成功';
    static PENDDING = '待定';
    static REJECTED = '失败';
    constructor(fun) {
        this.result = null;
        this.status = promiseComment.PENDDING;
        this.resolveCallbacks = []
        this.rejectCallbacks = []
        try {
            fun(this.resolve.bind(this), this.reject.bind(this))
        } catch (error) {
            this.reject(error)
        }
    }
    resolve(result) {
        setTimeout(() => {
            if (this.status === promiseComment.PENDDING) {
                this.status = promiseComment.FULLFILED;
                this.result = result
                this.resolveCallbacks.forEach((callback) => {
                    callback(result)
                })
            }
        });
    }
    reject(result) {
        setTimeout(() => {
            if (this.status === promiseComment.PENDDING) {
                this.status = promiseComment.REJECTED;
                this.result = result
                this.rejectCallbacks.forEach((callback) => {
                    callback(result)
                })
            }
        });

    }
    then(onfullfiled, onrejected) {
        return new promiseComment((resolve, reject) => {
          onfullfiled = typeof onfullfiled === 'function' ? onfullfiled : value => value;
          onrejected = typeof onrejected === 'function' ? onrejected : error => {
            throw error
          };

          if (this.status === promiseComment.PENDDING) {
            this.resolveCallbacks.push(() => {
              try {
                const x = onfullfiled(this.result);
                resolve(x);
              } catch (error) {
                reject(error);
              }
            });
            this.rejectCallbacks.push(() => {
              try {
                const x = onrejected(this.result);
                resolve(x);
              } catch (error) {
                reject(error);
              }
            });
          }

          if (this.status === promiseComment.FULLFILED) {
            setTimeout(() => {
              try {
                const x = onfullfiled(this.result);
                resolve(x);
              } catch (error) {
                reject(error);
              }
            });
          }
          if (this.status === promiseComment.REJECTED) {
            setTimeout(() => {
              try {
                const x = onrejected(this.result);
                resolve(x);
              } catch (error) {
                reject(error);
              }
            });
          }
        })
      }

}
let promiseComm = new promiseComment((resolve, reject) => {
    console.log('second')
    setTimeout(() => {
        console.log('one')
        resolve('社会主义好')
    })
    console.log('third');
})
promiseComm.then((res) => {
    console.log('res', res);
    console.log('99999');

}).then((res) => {
      console.log('第二个res')
    })

五、promise A+ 讲解

为了方便讲解和阅读,我会将代码拆解,代码中几个重要的点我会使用标注【1】这种形式在代码最后进行逐一讲解,另外标准的Promise实现中,调用resolvereject方法时,会将对应的回调函数放入异步队列中执行,而文中的异步请求均使用settimeout模拟,达到异步的效果~

// 定义了一个名为`promiseComment`的类,实现了一个简单的Promise

class promiseComment {
    // 类定义了三个静态属性:FULLFILED、PENDDING 和 REJECTED,
    // 它们分别表示Promise的三种状态:成功、待定和失败。
    static FULLFILED = '成功';
    static PENDDING = '待定';
    static REJECTED = '失败';
    constructor(fun) {
        this.result = null; // result表示Promise的结果
        this.status = promiseComment.PENDDING;// status表示Promise的状态
        this.resolveCallbacks = [] // 成功回调函数
        this.rejectCallbacks = [] // 失败回调函数
        try {
            // 标注【1】this指向问题
            // 调用传入的函数 resolve 和reject
            fun(this.resolve.bind(this), this.reject.bind(this)) 
        } catch (error) {
        // 捕获异常
            this.reject(error)
        }
    }
    
    //定义了两个方法:resolve和reject。这两个方法都接受一个参数,表示Promise的结果。
    resolve(result) {
        // 模拟请求
        setTimeout(() => {
        // 使用setTimeout将代码放入异步队列中执行。然后检查当前Promise的状态是否为PENDDING。
        // 如果是,则将状态分别设置为FULLFILED或REJECTED,并将结果保存到实例属性中。
        // 最后遍历回调函数数组,并依次调用每一个回调函数。
            if (this.status === promiseComment.PENDDING) {
                this.status = promiseComment.FULLFILED;
                this.result = result
                this.resolveCallbacks.forEach((callback) => {
                    callback(result)
                })
            }
        });
    }
    
    // 和resolve一样 不在缀述
    reject(result) {
        // 模拟请求
        setTimeout(() => {
            if (this.status === promiseComment.PENDDING) {
                this.status = promiseComment.REJECTED;
                this.result = result
                this.rejectCallbacks.forEach((callback) => {
                    callback(result)
                })
            }
        });

    }
    
        
    // 定义一个then的方法。这个方法接受两个参数:onfullfiled 和 onrejected,
    // 分别表示成功回调函数和失败回调函数。
    then(onfullfiled, onrejected) {
        // 首先创建一个新的Promise实例,并返回它。检查 onfullfiled 和 onrejected 是否函数类型。
        // 则分别赋值为一个返回其参数的函数和一个抛出错误的函数。标注【2】
      return new promiseComment((resolve, reject) => {
          onfullfiled = typeof onfullfiled === 'function' ? onfullfiled : value => value;
          onrejected = typeof onrejected === 'function' ? onrejected : error => {
            throw error
          };

          // 根据当前的状态执行不同的操作。
          
          // 如果当前状态为 PENDDING,则将两个回调函数分别添加到相应的回调数组中。
          // 这里使用了一个函数来包装回调函数,为了在执行回调函数时能够捕获错误
          // 并将回调函数的返回值传递给 resolve 函数。
          if (this.status === promiseComment.PENDDING) {
            this.resolveCallbacks.push(() => {
            // 使用 try...catch 语句来捕获可能抛出的错误。
            // 调用 onfullfiled 函数并传入 this.result作为参数。
            // 将 onfullfiled 函数的返回值赋值给 x 变量。
            // 调用 resolve 函数并传入 x 变量的值作为参数。
              try {
                const x = onfullfiled(this.result);
                resolve(x);
              } catch (error) {
              //如果在执行 onfullfiled 函数时抛出错误,那么这个错误会被 catch`语句捕获,
            // 并调用 reject函数来处理错误。
                reject(error);
              }
            });
            
            // 同理 不做解释
            this.rejectCallbacks.push(() => {
              try {
                const x = onrejected(this.result);
                resolve(x);
              } catch (error) {
                reject(error);
              }
            });
          }
            
          // 如果状态为FULLFILED,则调用成功回调函数。
          if (this.status === promiseComment.FULLFILED) {
            setTimeout(() => {
               
              try {
                const x = onfullfiled(this.result);
                resolve(x);
              } catch (error) {
                reject(error);
              }
            });
          }
          
          // 如果状态为REJECTED,则调用失败回调函数。
          if (this.status === promiseComment.REJECTED) {
            setTimeout(() => {
              try {
                const x = onrejected(this.result);
                resolve(x);
              } catch (error) {
                reject(error);
              }
            });
          }
        })
      }
   }

}
let promiseComm = new promiseComment((resolve, reject) => {
    console.log('second')
    setTimeout(() => {
        console.log('one')
        resolve('社会主义好')
    })
    console.log('third');
})
promiseComm.then((res) => {
    console.log('res', res);
    console.log('99999');

}).then((res) => {
      console.log('第二个res')
    })

second
third
one
res 社会主义好 标注【3
99999
第二个res

六、对文章内标注问题的解释

标注【1】:this指向问题

构造函数接受一个函数作为参数。在构造函数内部,调用这个函数时传入了两个方法:resolvereject。这两个方法都是定义在promiseComment类的原型上的,它们的this指向调用它们的实例对象。也就说谁调用就指向谁。

但是,当你将这两个方法作为参数传递给另一个函数时,它们的this指向会丢失。也就是说,当你在传入的函数内部调用这两个方法时,它们的this不再指向实例对象,而是指向全局对象(在浏览器中是window对象,在Node.js中是global对象)。

为了避免这个问题,所以使用bind方法来创建一个新的函数,并将它的this指向绑定到实例对象上。

fun(this.resolve.bind(this), this.reject.bind(this))
标注【2】:为什么需要return 一个新的promise

then 函数中返回一个新的 Promise 实例,是为了实现 Promise 的链式调用。

简单来说,then方法的执行,

当你调用一个 Promise 实例的 then 方法时,它会返回一个新的 Promise 实例。这样,你就可以在这个新的 Promise 实例上继续调用 then 方法,形成一个链式调用。每个 then 方法都可以接收一个回调函数作为参数,并在上一个 Promise 状态变为 FULLFILED 时执行这个回调函数。简单来说,如果 then 方法不返回一个新的 Promise 实例,那么就无法实现链式调用。

标注【3】:执行过程和输出结果的解释

创建了一个新的 promiseComment 实例,并传入一个函数作为参数。该函数会立即执行,并输出 second,定时器加入异步任务队列,执行同步代码输出third,

这里很多同学会觉得需要先输出res,

请注意:.then()中的回调函数依赖于resolve函数或者是reject函数的调用,也就是说他只有在resolve函数调用之后才会执行,并且第一个.then()中的res就是定时器中resolve的值,如果我们不resolve(),那么then()中的方法就不会执行。 所以输出结果为

second
third
one
res 社会主义好
99999
第二个res

转载自:https://juejin.cn/post/7243067221999976503
评论
请登录