JavaScript Promise原理与实现刨析
1 Promise核心逻辑实现
Promise对象是一个原生的javascript对象,是一种异步编程的解决方案,它通过一个回调,可以避免更多的回调,接下来说明其原理及实现。
下面一段代码是Promise的基本使用:
new Promise((resolve, reject) => { resolve("成功"); reject("失败"); }) Promise.then(value => { }, reason => { })
从上面的代码中,我们可以分析出一些关键点:
- Promise创建时需要使用
new
关键字,那么我们可以知道Promise就是一个类; - 在执行这个类的时候,需要传递一个执行器进去,这个执行器会立即执行;
- 在执行器中有两个参数,
resolve
和reject
,它们都是函数,用来改变Promise中的状态; - Promise中有三种状态,分别是:成功
fulfilled
、失败rejected
和等待pending
,状态只能从pending
—>fulfilled
,或者pending
—>rejected
,状态一旦确定就不可以更改; resolve
和reject
函数是用来更改状态的,其中,resolve
将状态更改为fulfilled
,reject
将状态更改为rejected
;then
方法接收两个函数作为参数,它需要判断状态,如果成功调用第一个回调函数,如果失败调用第二个回调函数,并且then
方法是被定义在原型对象中的,第一个回调函数的参数为成功之后的值,第二个回调函数的参数为失败之后的原因;
接下来我们根据上面分析出的内容,一步一步实现我们自己的Promise。
首先创建一个类,为constructor
构造函数传入一个执行器,因为执行器需要立即执行,因此在构造函数中调用该执行器。
class MyPromise { constructor(executor) { // 接收一个执行器 executor(); // 执行器会立即执行 } }
在执行器中有两个参数resolve
和reject
,它们都是函数,因此在类中创建两个箭头函数resolve
和reject
,在执行器executor
中使用this
来调用它们。
为什么使用箭头函数:
注意,我们在Promise中调用resolve
和reject
是直接调用的,如果将它们写成普通函数,那么会将this
指向window
或者undefined
,如果我们写成箭头函数,那么它们的this
就会指向类的实例对象。
class MyPromise { constructor(executor) { // 接收一个执行器 executor(this.resolve, this.reject); // 执行器会立即执行 } resolve = () => { } reject = () => { } }
resolve
和reject
这两个函数是用来改变状态的,因此我们将状态定义在类的外面,因为它们会被频繁使用到。在类中我们默认定义状态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; } } }
将成功回调和失败回调存储起来之后,我们则要在resolve
和reject
方法中判断是否存在成功或者失败的回调,如果存在,则将其调用。
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); } } }
更改成数组之后,那么我们原来在resolve
和reject
函数中调用成功或者失败的回调函数就不可以使用了,而是在其中循环调用数组中的回调函数。
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 !== PENDING) return; // 当状态不是等待,直接返回 this.status = REJECTED; // 将状态改为失败 this.reason = reason; // 将失败的值传递 while (this.failCallback.length) { // 循环执行 this.failCallback.shift()(this.value); // 调用失败回调函数 } } 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); } } }
4 链式调用then方法
Promise的then
方法可以链式调用,下一个then
方法中成功回调函数的参数是上一个then
方法中的回调函数的返回值,也就是说,在下面代码中,value
的值为1。
let promise = new Promise((resolve, reject) => { }); promise.then(() => { return 1 }) .then(value => { }) .then(() => { })
我们首先来实现then
方法的链式调用。then
方法是Promise中的方法,如果要实现链式调用,那么每个then
方法都应该返回一个Promise对象,这样才可以调用。那么我们应该在then
方法中创建一个Promise对象,最后返回这个对象就可以。除此之外,我们还需要将then
方法中原来的代码传入到新创建对象的执行器中,保证调用方法后就立即执行。
then(successCallback, failCallback) { // then方法有两个参数 let promise = new MyPromise(() => { if (this.status === FULFILLED) { // 成功调用第一个回调函数 successCallback(this.value); } else if (this.status === REJECTED) { // 失败调用第二个回调函数 failCallback(this.reason); } else { // 当状态为等待时,将成功回调和失败回调存储起来 this.successCallback.push(successCallback); this.failCallback.push(failCallback); } }) return promise; }
那么链式调用的问题就解决了,我们还需要将本次回调函数的返回值传到下一个then
的成功回调函数中。因此,我们需要获取到成功和失败回调函数的返回值,并将其通过新promise对象的resolve
方法传递过去。
then(successCallback, failCallback) { // then方法有两个参数 let promise = new MyPromise((resolve, reject) => { if (this.status === FULFILLED) { // 成功调用第一个回调函数 let result = successCallback(this.value); resolve(result); // 将返回值传递到下一个回调函数 } else if (this.status === REJECTED) { // 失败调用第二个回调函数 let result = failCallback(this.reason); resolve(result); } else { // 当状态为等待时,将成功回调和失败回调存储起来 this.successCallback.push(successCallback); this.failCallback.push(failCallback); } }) return promise; }
如果上一次回调函数的返回值是一个Promise对象,我们就需要查看Promise对象的状态,如果状态成功,则调用resolve
将状态传递给下一个Promise对象,如果状态失败,则调用reject
将失败信息传递。我们需要写一个方法resolvePromise
,用来判断这些逻辑。
function resolvePromise(result, resolve, reject) { // 通过判断result是不是MyPromise的实例对象来判断是不是Promise对象 if (result instanceof MyPromise) { // 是Promise对象 // 调用then方法查看Promise对象的状态 // 如果成功调用第一个回调函数,如果失败调用第二个回调函数 result.then(value => resolve(value), reason => reject(reason)); } else { // 如果是普通值 resolve(result); // 直接将普通值传递 } }
那么在then
方法中调用resolvePromise
函数:
then(successCallback, failCallback) { // then方法有两个参数 let promise = new MyPromise((resolve, reject) => { if (this.status === FULFILLED) { // 成功调用第一个回调函数 let result = successCallback(this.value); resolvePromise(result, resolve, reject); // 调用方法 } else if (this.status === REJECTED) { // 失败调用第二个回调函数 let result = failCallback(this.reason); resolvePromise(result, resolve, reject); } else { // 当状态为等待时,将成功回调和失败回调存储起来 this.successCallback.push(successCallback); this.failCallback.push(failCallback); } }) return promise; }
在上面我们知道then
方法中可以返回Promise对象,那么如果返回的Promise对象还是then
方法中接收到的Promise对象,则会形成循环调用,这时应该报出错误。我们在then
方法中拿到了返回值result
,因此只需要判断它是不是和promise
相同即可。我们将这个逻辑也写在resolvePromise
函数中。
function resolvePromise(promise, result, resolve, reject) { if (promise === result) { // 如果相同,报错 reject(new TypeError("promise对象循环了")); return; // 阻止代码向下执行 } // 通过判断result是不是MyPromise的实例对象来判断是不是Promise对象 if (result instanceof MyPromise) { // 是Promise对象 // 调用then方法查看Promise对象的状态 // 如果成功调用第一个回调函数,如果失败调用第二个回调函数 result.then(value => resolve(value), reason => reject(reason)); } else { // 如果是普通值 resolve(result); // 直接将普通值传递 } }
then
方法中也调用上面的函数。但是实际上我们在then
方法中是获取不到promise
的,因为promise
在实例化之后才可以获取到,这时我们可以将状态成功时的代码变成异步代码,让同步代码先执行完成,执行完成之后再执行异步代码。我们可以使用setTimeout
定时器来使其变成异步代码。
then(successCallback, failCallback) { // then方法有两个参数 let promise = new MyPromise((resolve, reject) => { if (this.status === FULFILLED) { // 成功调用第一个回调函数 setTimeout(() => { // 变成异步代码,获取promise let result = successCallback(this.value); resolvePromise(promise, result, resolve, reject); // 调用方法 }, 0) } else if (this.status === REJECTED) { // 失败调用第二个回调函数 setTimeout(() => { let result = failCallback(this.reason); resolvePromise(promise, result, resolve, reject); }, 0) } else { // 当状态为等待时,将成功回调和失败回调存储起来 this.successCallback.push(successCallback); this.failCallback.push(failCallback); } }) return promise; }
全部代码如下:
const PENDING = "pending"; // 等待 const FULFILLED = "fulfilled"; // 成功 const REJECTED = "rejected"; // 失败 class MyPromise { constructor(executor) { // 接收一个执行器 try { executor(this.resolve, this.reject); // 执行器会立即执行 } catch (e) { this.reject(e); // 将错误原因传递给失败回调函数 } } 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); // 如果成功回调存在,则调用 while (this.successCallback.length) { // 循环执行数组中的回调函数 this.successCallback.shift()(this.value); // 调用回调函数 } } reject = reason => { // reason是失败之后的原因 if (this.status !== PENDING) return; // 当状态不是等待,直接返回 this.status = REJECTED; // 将状态改为失败 this.reason = reason; // 将失败的值传递 // this.failCallback && this.failCallback(this.reason); // 如果失败回调存在,则调用 while (this.failCallback.length) { // 循环执行 this.failCallback.shift()(this.value); // 调用失败回调函数 } } then(successCallback, failCallback) { // then方法有两个参数 let promise = new MyPromise((resolve, reject) => { if (this.status === FULFILLED) { // 成功调用第一个回调函数 setTimeout(() => { // 变成异步代码,获取promise let result = successCallback(this.value); resolvePromise(promise, result, resolve, reject); // 调用方法 }, 0) } else if (this.status === REJECTED) { // 失败调用第二个回调函数 setTimeout(() => { let result = failCallback(this.reason); resolvePromise(promise, result, resolve, reject); }, 0) } else { // 当状态为等待时,将成功回调和失败回调存储起来 this.successCallback.push(successCallback); this.failCallback.push(failCallback); } }) return promise; } } function resolvePromise(promise, result, resolve, reject) { if (promise === result) { // 如果相同,报错 reject(new TypeError("promise对象循环了")); return; // 阻止代码向下执行 } // 通过判断result是不是MyPromise的实例对象来判断是不是Promise对象 if (result instanceof MyPromise) { // 是Promise对象 // 调用then方法查看Promise对象的状态 // 如果成功调用第一个回调函数,如果失败调用第二个回调函数 result.then(value => resolve(value), reason => reject(reason)); } else { // 如果是普通值 resolve(result); // 直接将普通值传递 } }
5 Promise错误捕获
1、捕获执行器错误。当执行器错误时,直接执行reject
方法,这个错误实际上是在then
方法中的失败回调函数中输出的。
constructor(executor) { // 接收一个执行器 try { executor(this.resolve, this.reject); // 执行器会立即执行 } catch (e) { this.reject(e); // 将错误原因传递给失败回调函数 } }
2、捕获then
方法中回调函数的错误。如果当前then
方法中的回调函数错误,那么应该在下一个then
方法中的失败回调函数中输出。
then(successCallback, failCallback) { // then方法有两个参数 let promise = new MyPromise((resolve, reject) => { if (this.status === FULFILLED) { // 成功调用第一个回调函数 setTimeout(() => { // 变成异步代码,获取promise try { let result = successCallback(this.value); resolvePromise(promise, result, resolve, reject); // 调用方法 } catch (e) { reject(e); // 将错误传递到下一个then中 } }, 0) } else if (this.status === REJECTED) { // 失败调用第二个回调函数 setTimeout(() => { try { let result = failCallback(this.reason); resolvePromise(promise, result, resolve, reject); } catch (e) { reject(e); // 传递错误 } }, 0) } else { // 当状态为等待时,将成功回调和失败回调存储起来 this.successCallback.push(successCallback); this.failCallback.push(failCallback); } }) return promise; }
当状态为等待时,也需要为它进行错误处理。状态为等待时,数组中原本存入了成功和失败的回调,但是这样没有办法进行错误处理,因此我们可以在数组中存入一个回调函数,回调函数内部调用成功或者失败的函数。
then(successCallback, failCallback) { // then方法有两个参数 let promise = new MyPromise((resolve, reject) => { if (this.status === FULFILLED) { // 成功调用第一个回调函数 setTimeout(() => { // 变成异步代码,获取promise try { let result = successCallback(this.value); resolvePromise(promise, result, resolve, reject); // 调用方法 } catch (e) { reject(e); // 将错误传递到下一个then中 } }, 0) } else if (this.status === REJECTED) { // 失败调用第二个回调函数 setTimeout(() => { try { let result = failCallback(this.reason); resolvePromise(promise, result, resolve, reject); } catch (e) { reject(e); // 传递错误 } }, 0) } else { // 当状态为等待时,将成功回调和失败回调存储起来 this.successCallback.push(() => { // 为数组添加成功回调函数 setTimeout(() => { // 变成异步代码,获取promise try { let result = successCallback(this.value); resolvePromise(promise, result, resolve, reject); // 调用方法 } catch (e) { reject(e); // 将错误传递到下一个then中 } }, 0) }); this.failCallback.push(() => { // 为数组添加失败回调函数 setTimeout(() => { try { let result = failCallback(this.reason); resolvePromise(promise, result, resolve, reject); } catch (e) { reject(e); // 传递错误 } }, 0) }); } }) return promise; }
那么在resolve
和reject
函数中,调用成功和失败函数的逻辑也需要修改一下:
// 局部代码 this.successCallback.shift()(); // 调用回调函数 this.failCallback.shift()(); // 调用失败回调函数
全部代码:
const PENDING = "pending"; // 等待 const FULFILLED = "fulfilled"; // 成功 const REJECTED = "rejected"; // 失败 class MyPromise { constructor(executor) { // 接收一个执行器 try { executor(this.resolve, this.reject); // 执行器会立即执行 } catch (e) { this.reject(e); // 将错误原因传递给失败回调函数 } } 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); // 如果成功回调存在,则调用 while (this.successCallback.length) { // 循环执行数组中的回调函数 this.successCallback.shift()(); // 调用回调函数 } } reject = reason => { // reason是失败之后的原因 if (this.status !== PENDING) return; // 当状态不是等待,直接返回 this.status = REJECTED; // 将状态改为失败 this.reason = reason; // 将失败的值传递 // this.failCallback && this.failCallback(this.reason); // 如果失败回调存在,则调用 while (this.failCallback.length) { // 循环执行 this.failCallback.shift()(); // 调用失败回调函数 } } then(successCallback, failCallback) { // then方法有两个参数 let promise = new MyPromise((resolve, reject) => { if (this.status === FULFILLED) { // 成功调用第一个回调函数 setTimeout(() => { // 变成异步代码,获取promise try { let result = successCallback(this.value); resolvePromise(promise, result, resolve, reject); // 调用方法 } catch (e) { reject(e); // 将错误传递到下一个then中 } }, 0) } else if (this.status === REJECTED) { // 失败调用第二个回调函数 setTimeout(() => { try { let result = failCallback(this.reason); resolvePromise(promise, result, resolve, reject); } catch (e) { reject(e); // 传递错误 } }, 0) } else { // 当状态为等待时,将成功回调和失败回调存储起来 this.successCallback.push(() => { // 为数组添加成功回调函数 setTimeout(() => { // 变成异步代码,获取promise try { let result = successCallback(this.value); resolvePromise(promise, result, resolve, reject); // 调用方法 } catch (e) { reject(e); // 将错误传递到下一个then中 } }, 0) }); this.failCallback.push(() => { // 为数组添加失败回调函数 setTimeout(() => { try { let result = failCallback(this.reason); resolvePromise(promise, result, resolve, reject); } catch (e) { reject(e); // 传递错误 } }, 0) }); } }) return promise; } } function resolvePromise(promise, result, resolve, reject) { if (promise === result) { // 如果相同,报错 reject(new TypeError("promise对象循环了")); return; // 阻止代码向下执行 } // 通过判断result是不是MyPromise的实例对象来判断是不是Promise对象 if (result instanceof MyPromise) { // 是Promise对象 // 调用then方法查看Promise对象的状态 // 如果成功调用第一个回调函数,如果失败调用第二个回调函数 result.then(value => resolve(value), reason => reject(reason)); } else { // 如果是普通值 resolve(result); // 直接将普通值传递 } }
6 then方法参数设置为可选
如果then
方法中没有参数,那么它的参数应该传到之后的then
方法中。
let p = new Promise((resolve, reject) => { resolve(100) }) p.then().then().then(value => console.log(value)); // 100
那么我们可以在then
方法中判断是否传入了参数,如果没有直接传到下一个then
方法中。
then(successCallback, failCallback) { // then方法有两个参数 // 如果没有传入参数,则将值返回 successCallback = successCallback ? successCallback : value => value; failCallback = failCallback ? failCallback : reason => reason => { throw reason }; let promise = new MyPromise((resolve, reject) => { if (this.status === FULFILLED) { // 成功调用第一个回调函数 setTimeout(() => { // 变成异步代码,获取promise try { let result = successCallback(this.value); resolvePromise(promise, result, resolve, reject); // 调用方法 } catch (e) { reject(e); // 将错误传递到下一个then中 } }, 0) } else if (this.status === REJECTED) { // 失败调用第二个回调函数 setTimeout(() => { try { let result = failCallback(this.reason); resolvePromise(promise, result, resolve, reject); } catch (e) { reject(e); // 传递错误 } }, 0) } else { // 当状态为等待时,将成功回调和失败回调存储起来 this.successCallback.push(() => { // 为数组添加成功回调函数 setTimeout(() => { // 变成异步代码,获取promise try { let result = successCallback(this.value); resolvePromise(promise, result, resolve, reject); // 调用方法 } catch (e) { reject(e); // 将错误传递到下一个then中 } }, 0) }); this.failCallback.push(() => { // 为数组添加失败回调函数 setTimeout(() => { try { let result = failCallback(this.reason); resolvePromise(promise, result, resolve, reject); } catch (e) { reject(e); // 传递错误 } }, 0) }); } }) return promise; }
7 实现Promise.all
Promise.all
方法接收一个数组作为参数,它允许我们按照异步代码调用的顺序得到异步代码的结果,也就是说,它的输出结果就是参数的传递顺序。这个方法有几点需要注意:
Promise.all
方法的返回值也是一个Promise对象,也就是说,它也可以链式调用then
方法;- 当
Promise.all
中所有结果都是成功的,那么结果就是成功的,如果有一个结果是失败的,那么它就是失败的;
Promise.all(["a", "b", p1(), p2(), "c"]).then(result => { // result -> ["a", "b", p1(), p2(), "c"] })
首先,我们看到all
方法是Promise
类名直接调用的,说明它是一个静态方法。它接受了一个数组作为参数,最终返回了一个Promise
对象,那么基本框架我们可以写出来了:
static all(array) { return new MyPromise((resolve, reject) => { }) }
接着我们应该判断传入的参数是普通值还是Promise对象,如果是普通值,那么直接放到结果数组中,如果是Promise对象,那么我们就先执行这个Promise对象,再将执行后的结果放到结果数组当中。将结果添加到结果数组中时,我们定义一个函数addData
来帮助我们。当循环执行完成之后,我们应该调用resolve
方法将result
结果数组传递到外面。
static all(array) { let result = []; // 结果数组,用来存放结果 function addData(key, value) { // 将结果添加到结果数组中 result[key] = value; } return new MyPromise((resolve, reject) => { for (let i = 0; i < array.length; i++) { let current = array[i]; // 获取当前的值 if (current instanceof MyPromise) { // 是一个Promise对象 // 如果是一个Promise对象,我们首先要执行这个对象 // 调用它的then方法,如果成功将结果添加到数组中,失败则传递错误 current.then(value => addData(i, value), reason => reject(reason)) } else { // 是一个普通值 addData(i, array[i]); // 普通值直接将结果添加到数组中 } } resolve(result); // 将结果传递出去 }) }
但是这里会出现一个问题,for循环是一瞬间执行完成了,如果Promise中有异步代码,那么异步代码并没有执行完就执行resolve
方法,那么我们最终拿不到异步任务的结果。所以我们可以设置一个计数器index
,当结果数组中有一个结果,就让计数器+1,当计数器的值等于数组array
的长度时,才能执行resolve
方法。
static all(array) { let result = []; // 结果数组,用来存放结果 let index = 0; // 计数器,记录是否执行完成 return new MyPromise((resolve, reject) => { // addData方法在数组中才能执行resolve function addData(key, value) { // 将结果添加到结果数组中 result[key] = value; index++; if (index === array.length) resolve(result); } for (let i = 0; i < array.length; i++) { let current = array[i]; // 获取当前的值 if (current instanceof MyPromise) { // 是一个Promise对象 // 如果是一个Promise对象,我们首先要执行这个对象 // 调用它的then方法,如果成功将结果添加到数组中,失败则传递错误 current.then(value => addData(i, value), reason => reject(reason)) } else { // 是一个普通值 addData(i, array[i]); // 普通值直接将结果添加到数组中 } } }) }
8 实现Promise.resolve
Promise.resolve
方法会将给定的值转换成Promise对象,也就是说它的返回值就是一个Promise对象。
Promise.resolve(10).then(value => console.log(value));
Promise.resolve
方法是一个静态方法,在该方法内部会判断参数是否为Promise对象,如果是,则原封不动返回,如果不是,就创建一个Promise对象,将给定的值包裹在Promise对象当中,最后返回即可。
static resolve(value) { if (value instanceof MyPromise) return value; return new MyPromise(resolve => resolve(value)); }
9 实现Promise.race
Promise.race
参数是数组,它会返回一个Promise对象,一旦某个参数先改变状态,那么直接就将该状态返回,也就是说,看谁执行的更快。
Promise.race(["a", "b", "c"]).then(value => console.log(value));
Promise.race
方法是一个静态方法,它接受一个数组作为参数,最后返回一个Promise对象,基础框架如下:
static race(array) { return new MyPromise((reslve, reject) => { }) }
循环遍历参数,返回第一个成功或者失败的结果。
static race(array) { return new MyPromise((resolve, reject) => { for (let i = 0; i < array.length; i++) { Promise.resolve(array[i]).then(value => resolve(value), reason => reject(reason)); } }) }
10 实现finally方法
finally
方法接受一个回调函数作为参数,它有一些特点:
- 无论最后Promise对象最终的状态是成功的还是失败的,该方法中回调函数都会被执行一次
finally
方法后可以链式调用then
方法拿到当前Promise对象最终返回的结果。
let promise = new Promise(); promise.finally(() => console.log("finally")).then(value => console.log(value));
首先我们要获得Promise对象的状态,我们可以调用then
方法来获得状态。由于无论状态成功还是失败,我们都要调用回调函数,因此在then
方法中成功和失败中都调用一次该回调函数。由于finally
返回Promise对象,then
方法也返回Promise对象,因此我们直接将then
方法返回即可。
finally(callBack) { return this.then(() => { callBack(); // 成功的回调函数中调用 }, () => { callBack(); // 失败的回调函数中调用 }) }
接着,我们需要在成功的回调函数中返回成功的值,在失败的回调函数中传递失败的原因。
finally(callBack) { return this.then(value => { callBack(); // 成功的回调函数中调用 return value; // 将成功的值返回 }, reason => { callBack(); // 失败的回调函数中调用 throw reason; // 将失败原因传递下去 }) }
如果在finally
后面的then
中返回了一个Promise对象,对象中有异步代码,那么我们应该等待异步代码执行完成之后,再继续执行后面的then
方法。所以我们应该判断callBack
的返回值是什么,如果是一个普通值,我们将其转换为Promise对象,等待其执行完成,如果是一个Promise对象,我们还是等待其执行完成。
finally(callBack) { return this.then(value => { return MyPromise.resolve(callBack()).then(() => value); }, reason => { return MyPromise.resolve(callBack()).then(() => { throw reason }); }) }
11 实现catch方法
catch
方法用来处理Promise对象最终为失败的情况,当我们调用then
方法是,是可以不传递失败回调的,那么失败回调会被catch
方法所捕获。
let promise = new Promise(); promise.then(value => console.log(value)).catch(reason => console.log(reason));
catch
方法接受一个回调函数作为参数,在其内部调用then
方法,并且不传递成功回调,只传递失败回调函数,最后将then
方法返回即可。
catch(failCallback) { return this.then(undefined, failCallback); }
12 全部代码展示
const PENDING = "pending"; // 等待 const FULFILLED = "fulfilled"; // 成功 const REJECTED = "rejected"; // 失败 class MyPromise { constructor(executor) { // 接收一个执行器 try { executor(this.resolve, this.reject); // 执行器会立即执行 } catch (e) { this.reject(e); // 将错误原因传递给失败回调函数 } } 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); // 如果成功回调存在,则调用 while (this.successCallback.length) { // 循环执行数组中的回调函数 this.successCallback.shift()(); // 调用回调函数 } } reject = reason => { // reason是失败之后的原因 if (this.status !== PENDING) return; // 当状态不是等待,直接返回 this.status = REJECTED; // 将状态改为失败 this.reason = reason; // 将失败的值传递 // this.failCallback && this.failCallback(this.reason); // 如果失败回调存在,则调用 while (this.failCallback.length) { // 循环执行 this.failCallback.shift()(); // 调用失败回调函数 } } then(successCallback, failCallback) { // then方法有两个参数 // 如果没有传入参数,则将值返回 successCallback = successCallback ? successCallback : value => value; failCallback = failCallback ? failCallback : reason => { throw reason }; let promise = new MyPromise((resolve, reject) => { if (this.status === FULFILLED) { // 成功调用第一个回调函数 setTimeout(() => { // 变成异步代码,获取promise try { let result = successCallback(this.value); resolvePromise(promise, result, resolve, reject); // 调用方法 } catch (e) { reject(e); // 将错误传递到下一个then中 } }, 0) } else if (this.status === REJECTED) { // 失败调用第二个回调函数 setTimeout(() => { try { let result = failCallback(this.reason); resolvePromise(promise, result, resolve, reject); } catch (e) { reject(e); // 传递错误 } }, 0) } else { // 当状态为等待时,将成功回调和失败回调存储起来 this.successCallback.push(() => { // 为数组添加成功回调函数 setTimeout(() => { // 变成异步代码,获取promise try { let result = successCallback(this.value); resolvePromise(promise, result, resolve, reject); // 调用方法 } catch (e) { reject(e); // 将错误传递到下一个then中 } }, 0) }); this.failCallback.push(() => { // 为数组添加失败回调函数 setTimeout(() => { try { let result = failCallback(this.reason); resolvePromise(promise, result, resolve, reject); } catch (e) { reject(e); // 传递错误 } }, 0) }); } }) return promise; } finally(callBack) { return this.then(value => { return MyPromise.resolve(callBack()).then(() => value); }, reason => { return MyPromise.resolve(callBack()).then(() => { throw reason }); }) } catch(failCallback) { return this.then(undefined, failCallback); } static all(array) { let result = []; // 结果数组,用来存放结果 let index = 0; // 计数器,记录是否执行完成 return new MyPromise((resolve, reject) => { function addData(key, value) { // 将结果添加到结果数组中 result[key] = value; index++; if (index === array.length) resolve(result); } for (let i = 0; i < array.length; i++) { let current = array[i]; // 获取当前的值 if (current instanceof MyPromise) { // 是一个Promise对象 // 如果是一个Promise对象,我们首先要执行这个对象 // 调用它的then方法,如果成功将结果添加到数组中,失败则传递错误 current.then(value => addData(i, value), reason => reject(reason)) } else { // 是一个普通值 addData(i, array[i]); // 普通值直接将结果添加到数组中 } } }) } static resolve(value) { if (value instanceof MyPromise) return value; return new MyPromise(resolve => resolve(value)); } static race(array) { return new MyPromise((resolve, reject) => { for (let i = 0; i < array.length; i++) { Promise.resolve(array[i]).then(value => resolve(value), reason => reject(reason)); } }) } } function resolvePromise(promise, result, resolve, reject) { if (promise === result) { // 如果相同,报错 reject(new TypeError("promise对象循环了")); return; // 阻止代码向下执行 } // 通过判断result是不是MyPromise的实例对象来判断是不是Promise对象 if (result instanceof MyPromise) { // 是Promise对象 // 调用then方法查看Promise对象的状态 // 如果成功调用第一个回调函数,如果失败调用第二个回调函数 result.then(value => resolve(value), reason => reject(reason)); } else { // 如果是普通值 resolve(result); // 直接将普通值传递 } }
到此这篇关于JavaScript Promise原理与实现刨析的文章就介绍到这了,更多相关JS Promise内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
相关文章
javascript设计模式 – 策略模式原理与用法实例分析
这篇文章主要介绍了javascript设计模式 – 策略模式,结合实例形式分析了javascript策略模式相关概念、原理、用法及操作注意事项,需要的朋友可以参考下2020-04-04GWT中复制到剪贴板 js+flash实现复制 兼容性比较好
今天看到有个Google Code的项目,叫ZeroClipboard,大意是使用flash作为媒介,将内容复制到剪贴板。这比用纯javascript好,因为不同浏览器会出于安全的原因,有不同反应,例如IE会给出提示,有的浏览器不支持复制到剪贴板。2010-03-03
最新评论