看了就会,保姆级带你一步步实现Promise的核心功能(二)

简介: 众所周知, promise 是前端面试中雷打不动的面试题了,面试官都很爱考。周一之前也是知识比较浮于表面,在一些面经上看到了 promise 的实现方式,就只停留在那个层面上。但实际上我发现,如果没有深入其原理去理解,面试官稍微变个法子来考,这道题很容易就把我给问倒了。所以呀,还是老老实实从头到尾研究一遍,这样等遇到了,不管怎么考,万宗不变其一,把原理理解了,就没有那么容易被问倒了。下面开始进入本文的讲解~🏷️

📑三、实现Promise的核心功能



1. 基础核心功能实现🏷️


(1)碎碎念


接下来我们先来实现 promise 最基础的核心功能,也就是 promise.resolve()promise.reject() 这两个函数。

注意:基础功能除了构造器 constructor 意以外,其余的实现都不是绑定在原型链上的函数,将会使用箭头函数来进行实现。


(2)Promise基础功能的分析


我们先来看下 promise 的基本使用是怎么样的,具体代码如下:

/**
 * 01_promise的基本使用
 */
const promise = new Promise(function(resolve, reject) {
    if (success) {
        resolve(value);
    } else {
        reject(error);
    }
});
复制代码

根据使用方式,我们可以得出 promise以下几个特点:

  • promise 是一个对象;
  • 当我们新建一个 promise 对象的同时,需要传进去一个回调函数;
  • 这个回调函数又需要接收两个回调函数 resolvereject ,且用这两个回调函数来作为参数,之后呢, 当调用成功时,使用 resolve 回调函数,而当调用失败时,使用 reject 回调函数。
  • resolvereject 这两个回调函数都将会被用来修改 promise 的状态, resolve 会把 pending 状态修改为 fulfilled ,而 reject 将会把 pending 状态修改为 rejected 。同时,值得注意的是,一旦状态确定后,后续所有操作的状态将不会再被更改,即不可逆


(3)Promise基础功能的实现


我们现在来实现 promise 的基本功能,该功能含有以下几个组成要素:

  • 实现 PromiseMon 的基础结构,其中包含构造函数和状态;
  • 实现 resolvereject 功能,这里先实现状态从 pendingfulfilledrejected 的改变,其余状态间的改变暂未实现。

具体实现代码如下:

/**
 * 02_promise基础功能的实现
 */
// 定义pending、fulfilled和rejected三个常量
const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';
class PromiseMon {
    // 定义Promise中的状态,默认状态为pending
    status = PENDING;
    // cb即callback,是传给promise的回调函数
    constructor(cb) {
        // cb这个回调函数会被立即执行,作用是判断状态是否应该进行更改
        cb(this.resolve, this.reject);
    }
    // 使用箭头函数的原因:箭头函数可以减少this指向造成的问题,将其绑定在promise的实例对象
    // resolve回调函数
    resolve = () => {
        // 只有当状态为pending时才能修改
        if(this.status != PENDING) {
            return;
        }else{
            this.status = FULFILLED;
        }
    };
    // reject回调函数
    reject = () => {
        只有当状态为pending时才能修改
        if(this.status != PENDING) {
            return;
        }else{
            this.status = REJECTED;
        }
    }
}
// 调用resolve和reject来验证状态在确定之后不可逆
const promise1 = new PromiseMon((resolve, reject) => {
    resolve('resolved');
    reject('rejected');
});
const promise2 = new PromiseMon((resolve, reject) => {
    reject('rejected');
    resolve('resolved');
});
console.log(promise1.status); // fulfilled
console.log(promise2.status); // rejected
复制代码


(4)thenable功能的分析


上面我们简单封装了 PromiseMon 这个函数,那现在呢,我们继续用它来实现 thenable 的功能。

大家都知道, promise 在调用了 resolvereject 方法之后,就该来触发后续的  .then 或者 .catch 方法了。如果没有这两个方法的话,那么 promise 返回的数据都没啥使用的地儿,那还返回这个数据来干嘛对吧。

我们现在先来看关于 then 的基本使用操作。具体代码如下:

const fs = require('fs');
const readFile = (filename) => {
    const promise = new Promise(function(resolve, reject){
        fs.readFile(filename, (err, data) => {
            if(err){
                reject(err);
            }else{
                resolve(data);
            }
        });
    });
    return promise;
}
const existedFile = readFile('./test.txt');
existedFile.then(
    (data) => {
        console.log('content: ', Buffer.from(data).toString());
    },
    (error) => {
        console.log(error);
    }
)
复制代码

综上代码,我们来分析 then 函数的几个特点:

  • then 函数接收两个参数,第一个参数在异步操作成功时进行调用,第二个则是在操作失败时调用。
  • then 函数需要能够分析 promise 的状态,分析完 promise 的状态后再决定去调用成功或失败的回调函数
  • then 方法被定义在原型对象上: Promise.prototype.then()
  • then 调用成功的回调函数时会接收一个成功的数据作为参数,同样地,当他调用失败的回调函数时,在此之前它也会接收到一个失败的原因来作为参数进行传递。
  • then 会先接收到一个成功状态的数据,那么这个数据就用来作为参数,这个参数供给处理成功状态的回调函数进行调用;同样地,当处理失败状态时, then 会先接收到一个失败状态的数据,之后这个数据用来作为参数,这个参数供给处理失败状态的回调函数进行调用。说的这么绕,总结一下就是:接收当前状态数据数据作为参数拿来给回调函数调用


(5)thenable功能的实现


我们现在来实现 thenable 的基本功能,该功能含有以下几个组成要素:

  • 将在原型上实现 then 方法;
  • 修改原先的 resolve 函数,实现对成功状态的数据进行绑定;
  • 修改原先的 reject 函数,实现对失败状态的原因进行绑定。

具体实现代码如下:

/**
 * 03_thenable功能的实现
 */
const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';
class PromiseMon {
    status = PENDING;
    // 定义成功和失败时的值,默认都是未定义
    value = undefined;
    reason = undefined;
    constructor(cb) {
        // cb这个回调函数会被立即执行,作用是判断状态是否应该进行更改
        cb(this.resolve, this.reject);
    }
    // 修改参数,让resolve接收成功后传来的值
    resolve = (value) => {
        if(this.status != PENDING) {
            return;
        }else{
            this.status = FULFILLED;
            // 将成功的值赋予给value
            this.value = value;
        }
    };
    // 修改参数,让reject接收失败后传来的原因
    reject = (reason) => {
        if(this.status != PENDING) {
            return;
        }else{
            this.status = REJECTED;
            // 将失败的原因赋予给reason
            this.reason = reason;
        }
    }
    /** then要接收两个回调函数,successCB这个回调函数在状态为fulfilled时使用,
     * 而failCB这个回调函数在状态为rejected时进行使用
     */ 
    then(successCB, failCB) {
        if(this.status === FULFILLED) {
            successCB(this.value);
        }else if(this.status === REJECTED) {
            failCB(this.reason);
        }
    }
}
// 测试用例
//成功状态测试
const successPromise = new PromiseMon((resolve, reject) => {
    resolve('successData');
    reject('failData');
});
console.log('成功状态:', successPromise.status); // 成功状态:successData
successPromise.then(
    (value) => {
        console.log('success:', value); // success:successData
    },
    (reason) => {
        console.log('error:', reason); // 没有输出
    }
)
//失败状态测试
const failPromise = new PromiseMon((resolve, reject) => {
    reject('failData');
    resolve('successData');
});
console.log('失败状态:', failPromise.status); // 失败状态:failData
failPromise.then(
    (value) => {
        console.log('success:', value); // 没有输出
    },
    (reason) => {
        console.log('error:', reason); // error:failData
    }
)
复制代码

到这里,我们就实现了一个最基础的、且同步执行的 Promise 。接下来我们来为这个同步的 Promise 添加异步逻辑。


2. 添加异步逻辑功能实现🏷️


(1)then中添加异步逻辑功能的分析


一般来说,我们在 promise 中被调用的大部分都是异步函数,比如 setTimeoutsetInterval 等等。所以呢,我们现在要在 then 中添加异步的功能,来实现对异步逻辑进行操作。

在上面的 then 方法中,大家定位到 if……else if…… 部分,上面所写的逻辑只有对状态为 fulfilledrejected 时才进行判断,而没有对状态为 pending 时进行判断。

所以,当状态为 pending 时,意味着 PromiseMon 中的异步函数还没有执行完毕。这个时候,我们需要将 succesCBfailCB 这两个回调函数给先存到一个变量中去,等到后续异步内容结束后再进行调用。


(2)then中添加异步逻辑功能的实现


依据上面的分析,我们来实现这个异步功能。具体代码如下:

/**
 * 04_添加异步逻辑功能的实现
 */
const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';
class PromiseMon {
    status = PENDING;
    value = undefined;
    reason = undefined;
    // 定义两个变量,来存放成功和失败时的回调函数
    successCB = undefined;
    failCB = undefined;
    constructor(cb) {
        cb(this.resolve, this.reject);
    }
    resolve = (value) => {
        if(this.status != PENDING) {
            return;
        }else{
            this.status = FULFILLED;
            this.value = value;
            /**
             * 表达式a && 表达式b:
             * 计算表达式a的运算结果,
             * 如果为true,执行表达式b,并返回b的结果;
             * 如果为false,返回a的结果。
             */
            /**
             * 当successCB里面有存放成功的回调函数时,则表明this.successCB为true,
             * 继续判断新传来的值的状态是否为成功状态的值,
             * 如果是,则将新的值传给this.successCB回调函数,
             * 如果否,则返回原来存放着的this.success的结果
             */ 
            this.successCB && this.successCB(this.value);
        }
    };
    reject = (reason) => {
        if(this.status != PENDING) {
            return;
        }else{
            this.status = REJECTED;
            this.reason = reason;
            // 存放调用失败的回调函数
            this.failCB && this.failCB(this.reason);
        }
    }
    /** then要接收两个回调函数,successCB这个回调函数在状态为fulfilled时使用,
     * 而failCB这个回调函数在状态为rejected时进行使用
     */ 
    then(successCB, failCB) {
        if(this.status === FULFILLED) {
            successCB(this.value);
        }else if(this.status === REJECTED) {
            failCB(this.reason);
        }
        // 当函数还没有执行完毕时,只能等待
        else{ 
            // 将两个回调函数的值存放起来
            this.successCB = successCB;
            this.failCB = failCB;
        }
    }
}
// 测试用例
// 测试异步成功状态
const asyncPromise1 = new PromiseMon((resolve, reject) => {
    setTimeout(() => {
        resolve('asyncSuccessData');
    }, 2000);
});
asyncPromise1.then(
    (value) => {
        console.log('异步成功状态:', value);
    },
    (reason) => {
        console.log('异步失败状态:', reason);
    }
);
// 测试异步失败状态
const asyncPromise2 = new PromiseMon((resolve, reject) => {
    setTimeout(() => {
        reject('asyncErrorData');
    }, 1000);
});
asyncPromise2.then(
    (value) => {
        console.log('异步成功状态:', value);
    },
    (reason) => {
        console.log('异步失败状态:', reason);
    }
);
/**
 * 打印结果:
 * 异步失败状态: asyncErrorData
 * 异步成功状态: asyncSuccessData
 */
复制代码

到这里,异步的功能我们也就实现啦!但是上面的 then 我们还只是实现 then 方法的一次调用,接下来我们来实现 then 方法的多次调用。


3. 实现then方法的多次调用🏷️


(1)多次调用then方法的功能分析

多次调用 then 方法分为两种情况:

  • 同步调用 then 方法。同步调用 then 方法相对比较简单,只要直接调用 successCBfailCB 回调函数即可。
  • 异步调用 then 方法。之前的属性 successCBfailCB 两个回调函数是存放为对象形式,因此,我们需要先优化我们的存储形式。优化完成之后,将所有的回调函数全部存放在一起,等到执行完毕之后再依次调用。


(2)多次调用then方法的功能实现

依据上述的分析,我们来实现多次调用 then 方法的功能。具体代码如下:

/**
 * 05_多次调用then方法功能的实现
 */
 const PENDING = 'pending';
 const FULFILLED = 'fulfilled';
 const REJECTED = 'rejected';
 class PromiseMon {
    status = PENDING;
    value = undefined;
    reason = undefined;
    // 定义两个数组变量,来各自存放成功和失败时的所有回调函数
    successCB = [];
    failCB = [];
    constructor(cb) {
        cb(this.resolve, this.reject);
    }
    resolve = (value) => {
        if(this.status != PENDING) {
            return;
        }else{
            this.status = FULFILLED;
            this.value = value;
            // 使用 shift()方法,来弹出并返回第一个元素
            while(this.successCB.length){
                this.successCB.shift()(this.value);
            }
        }
    };
    reject = (reason) => {
        if(this.status != PENDING) {
            return;
        }else{
            this.status = REJECTED;
            this.reason = reason;
            // 使用 shift()方法,来弹出并返回第一个元素
            while(this.failCB.length){
                this.failCB.shift()(this.reason);
            }
        }
    }
    then(successCB, failCB) {
        if(this.status === FULFILLED) {
            successCB(this.value);
        }else if(this.status === REJECTED) {
            failCB(this.reason);
        }else{
            // 通过push方法将回调函数的值存放到数组中
            this.successCB.push(successCB);
            this.failCB.push(failCB);
        }
    }
 }
// 测试用例
const multiplePromise1 = new PromiseMon((resolve, reject) => {
    setTimeout(() => {
        resolve('multiSuccessData');
    }, 2000);
});
multiplePromise1.then((value) => {
    console.log('第一次调用成功:', value); // 第一次调用成功: multiSuccessData
});
multiplePromise1.then((value) => {
    console.log('第二次调用成功:', value); // 第二次调用成功: multiSuccessData
});
/**
 * 打印结果:
 * 第一次调用成功: multiSuccessData
 * 第二次调用成功: multiSuccessData
 */
复制代码

讲到这里,关于对此调用 then 方法的功能就实现完成了。现在,我们继续来实现关于 then 方法的链式调用。


4. 实现then方法的链式调用🏷️



(1)then方法链式调用的功能分析

我们先来对 then 方法的链式调用进行功能分析具体如下:

  • 不考虑其他功能的前提下,先完成链式调用的嵌套
  • 实现链式调用的大前提是,每一个 then 函数返回的都必须是一个 Promise 对象,否则就无法衔接地去使用 then 函数。
  • 因此,首先我们需要在 then 函数中新建一个 Promise 对象,之后呢,在新建的 promise 对象里面,去处理内部使用的 resolvereject 所返回的值,最终 then 函数也就返回了 promise 对象。


(2)then方法链式调用的功能实现

依据上面的功能分析,我们来实现 then 的链式调用功能。具体代码如下:

/**
 * 06_then的链式调用功能的实现
 */
 const PENDING = 'pending';
 const FULFILLED = 'fulfilled';
 const REJECTED = 'rejected';
 class PromiseMon {
    status = PENDING;
    value = undefined;
    reason = undefined;
    // 定义两个数组变量,来各自存放成功和失败时的所有回调函数
    successCB = [];
    failCB = [];
    constructor(cb) {
        cb(this.resolve, this.reject);
    }
    resolve = (value) => {
        if(this.status != PENDING) {
            return;
        }else{
            this.status = FULFILLED;
            this.value = value;
            while(this.successCB.length){
                this.successCB.shift()(this.value);
            }
        }
    };
    reject = (reason) => {
        if(this.status != PENDING) {
            return;
        }else{
            this.status = REJECTED;
            this.reason = reason;
            while(this.failCB.length){
                this.failCB.shift()(this.reason);
            }
        }
    }
    then(successCB, failCB) {
        // 
        /**
         * 新建一个promise对象,来给下一个then使用。
         * 三种状态:
         * ①promise对象执行成功,调用resolve;
         * ②promise对象执行失败,则调用reject;
         * ③promise对象还未执行,将回调函数推入准备好的数组中。
         */
        const thenablePromise = new PromiseMon((resolve, reject) => {
            if(this.status === FULFILLED) {
                const thenableValue = successCB(this.value);
                // 判断返回的值是否是promise对象
                resolvePromise(thenableValue, resolve, reject);
            }else if(this.status === REJECTED) {
                const thenableReason = failCB(this.reason);
                // 判断返回的值是否是promise对象
                resolvePromise(thenableReason, resolve, reject);
            }else{
                // 通过箭头函数的方式将回调函数的值存放进数组中
                this.successCB.push(() => {
                    const thenableValue = successCB(this.value);
                    resolvePromise(thenableValue, resolve, reject);
                });
                this.failCB.push(() => {
                    const thenableReason = failCB(this.reason);
                    resolvePromise(thenableReason, resolve, reject);
                });
            }
        });
        return thenablePromise;
    }
}
/**
 * 判断传进来的thenablePromise是否是promise对象,
 * 如果是,则调用then函数来处理;如果否,则直接返回值。
 */
const resolvePromise = (thenablePromise, resolve, reject) => {
    // 判断是否是一个promise对象
    if(thenablePromise instanceof PromiseMon) {
        thenablePromise.then(resolve, reject);
    } else {
        // 如果不是promise对象,则直接返回值
        resolve(thenablePromise);
    }
}
// 测试用例
// 测试链式调用成功状态
const thenablePromise = new PromiseMon((resolve, reject) => {
    resolve('thenableSuccessData');
});
const otherPromise = () => {
    return new PromiseMon((resolve, reject) => {
        setTimeout(() => {
            resolve('otherPromise');
        }, 2000);
    });
}
const anotherPromise = () => {
    return new PromiseMon((resolve, reject) => {
        setTimeout(() => {
            resolve('anotherPromise');
        });
    });
}
thenablePromise
    .then((value) => {
        console.log('第一次链式调用成功:', value, new Date()); // 第一次调用成功: thenableSuccessData
        // return的结果是为了给下一个then使用
        return otherPromise();
    })
    .then((value) => {
        console.log('第二次链式调用成功:', value, new Date()); // 第二次调用成功: otherPromise
        return anotherPromise();
    })
    .then((value) => {
        console.log('第三次链式调用成功:', value, new Date()); // 第三次调用成功: anotherPromise
    })
/**
 * 打印结果:
 * 第一次链式调用成功: thenableSuccessData 2021-08-04T11:13:25.868Z
 * 第二次链式调用成功: otherPromise 2021-08-04T11:13:25.877Z
 * 第三次链式调用成功: anotherPromise 2021-08-04T11:13:25.878Z
 */
复制代码

至此,我们就完成了 promise 的链式调用。


(3)链式调用的自我检测

有时候我们有可能在调用 promise 时,会陷入自我调用的境地。也就是无限的循环嵌套无限的自我调用比如下面这种情况:

const promise1 = new Promise((resolve, reject) => {
  resolve('success');
});
// 无限循环嵌套,无限自我调用
// 当运行时控制台会抛出异常
const promise2 = promise1.then((val) => {
  return promise2;
});
// 打印结果:
//  TypeError: Chaining cycle detected for promise #<Promise>
复制代码

因此,现在我们要做的是,在 resolvePromise 函数中新增一个参数,这个参数就是当前所创造的 promise 对象。之后判断两个 promise 对象是否相等,如果相等,那么就抛出异常。依据这个逻辑,我们来修改上面链式调用的功能代码,达到禁止自我调用的闭环。具体代码如下:

/**
 * 07_链式调用的自我检测
 */
const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';
class PromiseMon {
  status = PENDING;
  value = undefined;
  reason = undefined;
  successCB = [];
  failCB = [];
  //此处省略constructor代码
  //此处省略resolve代码
  //此处省略reject代码
  then(successCB, failCB) {
      const thenablePromise = new PromiseMon((resolve, reject) => {
        /**
         * 利用setTimeout是异步函数的特性,
         * 这样setTimeout里面的内容会在同步函数执行完之后才会进行,
         * 所以,当resolvePromise在执行的时候,thenablePromise就已经被实例化了,
         * 使得resolvePromise顺利的调用thenablePromise
         */
          if(this.status === FULFILLED) {
            setTimeout(() => {
              const thenableValue = successCB(this.value);
              resolvePromise(thenablePromise, thenableValue, resolve, reject);
            }, 0);
          }else if(this.status === REJECTED) {
            setTimeout(() => {
              const thenableReason = failCB(this.reason);
              resolvePromise(thenablePromise, thenableReason, resolve, reject);
            }, 0);
          }else {
            this.successCB.push(() => {
              setTimeout(() => {
                const thenableValue = successCB(this.value);
                resolvePromise(thenablePromise, thenableValue, resolve, reject);
              }, 0);
            });
            this.failCB.push(() => {
              setTimeout(() => {
                const thenableReason = failCB(this.reason);
                resolvePromise(thenablePromise, thenableReason, resolve, reject);
              }, 0);
            });
          }
      });
      return thenablePromise;
  }
const resolvePromise = (createPromise, thenablePromise, resolve, reject) => {
  if(createPromise === thenablePromise) {
    return reject(new TypeError('出现循环调用的情况 Chaning cycle detected'))
  }else if(thenablePromise instanceof PromiseMon) {
      thenablePromise.then(resolve, reject);
  } else {
      resolve(thenablePromise);
  }
}
// 测试用例
// 测试自我调用
const thenablePromise = new PromiseMon((resolve, reject) => {
  resolve('chainningData');
});
const chainingPromise = thenablePromise.then((value) => {
  console.log('数据调用成功', value, new Date());
  return chainingPromise;
})
//会报错,出现循环调用
chainingPromise
  .then(
    (value) => {
      console.log('执行操作成功', value, new Date());
    },
    (reason) => {
      console.log('执行操作失败', reason, new Date());
    }
);
/*
打印结果:
数据调用成功 chainningData 2021-08-04T11:28:39.984Z
执行操作失败 TypeError: 出现循环调用的情况 Chaning cycle detected
*/
复制代码

至此,我们完成了链式调用的自我检测。


5. promise的错误处理🏷️


(1)错误处理场景


到这里,我们对 pormisethen 方法基本实现的差不多。但是还有一个很重要但是又很容易被我们疏忽的问题就是,错误处理。现在,我们来分析一下可能会出现异常的常见场景:

  • 构造器中的回调函数 cb ,需进行 try/catch 处理;
  • then 函数中的错误处理,需要对同步函数和异步函数进行 try/catch 处理。


(2)错误处理功能实现

依据上面的场景分析,我们来实现 promise 的错误处理功能。具体代码如下:

/**
 * 08_promise的错误处理
 */
const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';
class PromiseMon {
  status = PENDING;
  value = undefined;
  reason = undefined;
  successCB = [];
  failCB = [];
  constructor(cb) {
    try {
        cb(this.resolve, this.reject);
    } catch (err) {
        this.reject('err in cb');
    }
  }
  //此处省略resolve代码
  //此处省略reject代码
  then(successCB, failCB) {
      const thenablePromise = new PromiseMon((resolve, reject) => {
        //   给setTimeout这个异步函数添加try/catch
          if(this.status === FULFILLED) {
            setTimeout(() => {
              try {
                const thenableValue = successCB(this.value);
                resolvePromise(thenablePromise, thenableValue, resolve, reject);
              } catch (err) {
                  reject(err);
              }
            }, 0);
          }else if(this.status === REJECTED) {
            setTimeout(() => {
              try {
                const thenableReason = failCB(this.reason);
                resolvePromise(thenablePromise, thenableReason, resolve, reject);
              } catch (err) {
                  reject(err);
              }
            }, 0);
          }else {
              // resolvePromise 同时要加到successCB和failCB中进行处理
              this.successCB.push(() => {
                  setTimeout(() => {
                      try {
                        const thenableValue = successCB(this.value);
                        resolvePromise(thenablePromise, thenableValue, resolve, reject);
                      } catch (err) {
                          reject(err);
                      }
                  }, 0);
              });
              this.failCB.push(() => {
                  setTimeout(() => {
                    try {
                        const thenableReason = failCB(this.reason);
                        resolvePromise(thenablePromise, thenableReason, resolve, reject);
                    } catch (err) {
                        reject(err);
                    }
                  }, 0);
              });
          }
      });
      return thenablePromise;
  }
  // 此处省略resolvePromise代码
// 测试用例
const promise = new PromiseMon((resolve, reject) => {
    setTimeout(() => {
        resolve('successData');
    });
});
promise
    .then(
        (value) => {
            console.log(value); // (1) 打印 'successData'
            throw new Error('error'); // (2) 抛出异常
        },
        (reason) => {
            console.log(reason);
            return 'fail in then';
        }
    )
    .then(
        (value) => {
            console.log(value);
        },
        (reason) => {
            console.log(reason);
            return 'callback fail'; // 抛出异常后返回 'callback fail' 给下面的then方面调用
        }
    )
    .then(
        (value) => {
            console.log(value);
        },
        (reason) => {
            console.log(reason); // (3)上面传来callback fail,因此打印 'callback fail'
        }
    );
/*
打印结果:
successData
Error: error
callback fail
*/
复制代码

大家可以看到,通过 try/catch 的方式,对遇到的错误进行处理,并且最终抛出异常以及返回 reason 的值。


6. 实现then方法的参数可选🏷️


(1)参数可选实现思路

大家可以发现,上面我们在调用 then 方法的时候,一直都是需要进行参数传递的,这样看起来好像还不是特别友好。因此呢,我们现在来实现这个功能,让 then 方法的参数可以有传或者不传2 种操作。实现思路也比较简单,就是在 then 函数中判断是否传入参数,如果没有的话,则返回原来的 value 就好了。类似于下面这种形式:

promise
  .then((val) => val) // 这样使用箭头函数表明直接返回 value
  .then((val) => val) 
  .then((val) => val) 
  .then((val) => {
    console.log(val); // 200
  });
复制代码


(2)参数可选功能实现

依据上面的是实现思路,接下来我们来实现这个功能。具体代码如下:

下面我们对 then 函数进行改造:

then(successCB, failCB) {
    successCB = successCB ? successCB : (value) => value;
    failCB = failCB ? failCB : (reason) => { 
        throw reason;
    };
}
复制代码

来用两组测试用例进行测试:

// 测试用例
// 成功状态下的用例
 const successpromise = new PromiseMon((resolve, reject) => {
     resolve(100);
 });
 successpromise
    .then()
    .then()
    .then()
    .then((val) => {
        console.log(val); // 100
    });
// 失败状态下的用例
const failPromise = new PromiseMon((resolve, reject) => {
    reject(200);
});
failPromise
    .then()
    .then()
    .then()
    .then(
        (val) => {},
        (reason) => {
            console.log(reason); // 200
        }
    );
/**
 * 打印结果:
 * 100
 * 200
*/
复制代码

大家可以看到,不管是 resolve 还是 reject 状态,都一一完成了对参数可选功能的实现。


7. 实现Promise.all🏷️


(1)Promise.all功能分析

上面在讲 Promise 异步方案的时候就已经讲过 promise.allpromise.race 方法。现在,我们来梳理下实现思路:

  • Promise.all 是一个静态方法,它接收一个 Promise 数组 作为参数。
  • Promise.all 的特点在于,它可以按照顺序去获取所有调用的异步函数。
  • js关键字 static 将会把对应的变量或函数绑定到class类上,而不是绑定在 ** prototype 原型**上。


(2)Promise.all功能实现

依据实现的这个逻辑,来实现 promise.all 这个功能。具体代码如下:

/**
 * 10_promise.all功能的实现
 */
const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';
class PromiseMon {
    status = PENDING;
    value = undefined;
    reason = undefined;
    successCB = [];
    failCB = [];
    //省略constructor、resolve、reject和then方法的代码
    static all(arr){
        const results = [];
        let index = 0;
        return new PromiseMon((resolve, reject) => {
            // 添加数据的逻辑,把指定的数据添加到数组的对应位置上
            const addData = (idx, val) => {
                results[idx] = val;
                index++;
                // 进行这一步判断的目的:为了等待异步操作完成
                if(index === arr.length) {
                    resolve(results);
                }
            }
            // 对数组进行循环,获取所有的数据
            arr.forEach((cur, index) => {
                // 如果传进来的值是一个promise对象
                if(cur instanceof PromiseMon){
                    cur.then(
                        (value) => addData(index, value),
                        (reason) => reject(reason)
                    )
                }
                // 如果传进来的是普通值,而非promise对象
                else {
                    addData(index, cur);
                }
            });
        });
    }
}
// 此处省略resolvePromise代码
// 测试用例
const promise = () => {
    return new PromiseMon((resolve, reject) => {
        resolve(100);
    });
}
const promise2 = () => {
    return new PromiseMon((resolve, reject) => {
        setTimeout(() => {
            resolve(200);
        }, 1000);
    });
}
//因为使用static关键字,所以可以直接在类上面进行调用
PromiseMon.all(['a', 'b', promise(), promise2(), 'c']).then((res) => {
    console.log(res); // [ 'a', 'b', 100, 200, 'c' ]
})
复制代码

大家可以看到,即使 promise2 是异步函数,但最终也正常的显示在数组当中,且按序的一一进行打印。到此,也就说明 promise.all 成功实现啦!

同时, promise.race 也是按照这个模式去实现,这里不再进行讲解~


8. 实现Promise.resolve🏷️


(1)Promise.resolve功能分析

我们先来梳理下 promise.resolve实现思路:

  • 如果参数是一个 promise 实例,那么 promise.resolve() 将不做任何修改,原封不动地返回这个实例。
  • 参数不是 promise 实例,或根本不是一个对象,则 Promise.resolve() 方法返回一个新的 promise 对象,此时状态为 fulfilled
  • 不带有任何参数,则直接返回一个 fulfilled 状态的 promise 对象。


(2)Promise.resolve功能实现

依据以上的功能分析,来实现 promise.resolve 这个功能。具体代码如下:

/**
 * 11_promise.resolve功能的实现
 */
const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';
class PromiseMon {
    status = PENDING;
    value = undefined;
    reason = undefined;
    successCB = [];
    failCB = [];
    //省略constructor、resolve、reject和then方法的代码
    static resolve(value){
        // 传进来的是一个promise对象,原封不动返回
        if(value instanceof PromiseMon) {
            return value;
        } 
        // 传进来的不是promise对象,将其作为参数返回一个promise
        else {
            return new PromiseMon((resolve, reject) => {
                resolve(value);
            })
        }
    }
}
// 此处省略resolvePromise代码
// 测试用例
const promise = () => {
    return new PromiseMon((resolve, reject) => {
        resolve(100);
    });
}
// 1.参数是一个promise实例,那么不做任何修改,原封不动地返回这个实例
PromiseMon.resolve(promise).then((res) => {
    console.log(res); // 100
})
/**
 * 2.参数不是具有then方法的对象,或根本就不是对象,
 * 则Promise.resolve()方法返回一个新的promise对象,状态为fulfilled
 */
PromiseMon.resolve(200).then((res) => {
    console.log(res); // 200
})
/**
 * 3.不带有任何参数,则直接返回一个resolved状态的promise对象
 */
PromiseMon.resolve().then(function () {
    console.log('two'); // two
});
复制代码

大家可以看到,依据我们所罗列的三种情况, promise.resolve 的功能也一一实现啦!

同时, promise.reject 也是按照这个模式去实现,这里不再进行讲解~


📋四、温故而知新


最后,我们再用一张思维导图来回顾本文的知识点。详情见下图👇

2.png


📝五、结束语


写到这里的时候,发现我已经花了整整三天的时间,大约接近34h+在 promise 这个知识上,好在最终算是对 promise 核心功能的的实现有一个较为满意的结果。

可能也是第一次这么细致的去啃一个知识,所以在学习过程中遇到很多以前没踩过的坑,中间过程中对问题进行详细记录并尝试解决,慢慢的就完善了一个新的知识体系。


相关文章
|
3月前
|
前端开发 JavaScript
用原生JavaScript(ES5)来实现Promise的等效功能(异步回调)
用原生JavaScript(ES5)来实现Promise的等效功能(异步回调)
|
消息中间件 前端开发 JavaScript
看了就会,保姆级带你一步步实现Promise的核心功能(一)
众所周知, promise 是前端面试中雷打不动的面试题了,面试官都很爱考。周一之前也是知识比较浮于表面,在一些面经上看到了 promise 的实现方式,就只停留在那个层面上。但实际上我发现,如果没有深入其原理去理解,面试官稍微变个法子来考,这道题很容易就把我给问倒了。所以呀,还是老老实实从头到尾研究一遍,这样等遇到了,不管怎么考,万宗不变其一,把原理理解了,就没有那么容易被问倒了。 下面开始进入本文的讲解~🏷️
看了就会,保姆级带你一步步实现Promise的核心功能(一)
|
3月前
|
前端开发 JavaScript
如何处理 JavaScript 中的异步操作和 Promise?
如何处理 JavaScript 中的异步操作和 Promise?
37 1
|
3月前
|
前端开发 JavaScript
在JavaScript中,什么是promise、怎么使用promise、怎么手写promise
在JavaScript中,什么是promise、怎么使用promise、怎么手写promise
59 4
|
3月前
|
前端开发 JavaScript 开发者
JavaScript 中的异步编程:Promise 和 Async/Await
在现代的 JavaScript 开发中,异步编程是至关重要的。本文将介绍 JavaScript 中的异步编程概念,重点讨论 Promise 和 Async/Await 这两种常见的处理异步操作的方法。通过本文的阐述,读者将能够更好地理解和应用这些技术,提高自己在 JavaScript 开发中处理异步任务的能力。
|
2月前
|
前端开发 JavaScript 开发者
JavaScript进阶-Promise与异步编程
【6月更文挑战第20天】JavaScript的Promise简化了异步操作,从ES6开始成为标准。Promise有三种状态:pending、fulfilled和rejected。基本用法涉及构造函数和`.then`处理结果,如: ```javascript new Promise((resolve, reject) =&gt; { setTimeout(resolve, 2000, &#39;成功&#39;); }).then(console.log); // 输出: 成功
|
3月前
|
JSON 前端开发 JavaScript
【JavaScript技术专栏】JavaScript异步编程:Promise、async/await解析
【4月更文挑战第30天】JavaScript中的异步编程通过Promise和async/await来解决回调地狱问题。Promise代表可能完成或拒绝的异步操作,有pending、fulfilled和rejected三种状态。它支持链式调用和Promise.all()、Promise.race()等方法。async/await是ES8引入的语法糖,允许异步代码以同步风格编写,提高可读性和可维护性。两者结合使用能更高效地处理非阻塞操作。
43 0
|
1月前
|
前端开发 JavaScript
JavaScript异步编程:Promise与async/await的深入探索
【7月更文挑战第9天】Promise和async/await是JavaScript中处理异步编程的两大利器。Promise为异步操作提供了统一的接口和链式调用的能力,而async/await则在此基础上进一步简化了异步代码的书写和阅读。掌握它们,将使我们能够更加高效地编写出清晰、健壮的异步JavaScript代码。
|
1月前
|
前端开发 JavaScript 定位技术
JavaScript 等待异步请求数据返回值后,继续执行代码 —— async await Promise的使用方法
JavaScript 等待异步请求数据返回值后,继续执行代码 —— async await Promise的使用方法
28 1
|
2月前
|
前端开发 JavaScript
Promise是JavaScript解决异步问题的构造器,代表未来的不确定值。
【6月更文挑战第27天】Promise是JavaScript解决异步问题的构造器,代表未来的不确定值。它避免了回调地狱,通过链式调用`.then()`和`.catch()`使异步流程清晰。
33 2