「3.4w字」超保姆级教程带你实现Promise的核心功能

简介: 该文章通过详细的步骤和示例代码,逐步介绍了如何从零开始实现一个符合ECMAScript标准的Promise对象,涵盖了Promise的基本使用、状态管理、链式调用、错误处理机制及Promise.all和Promise.resolve等方法的实现。

promise

📚序言

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

下面开始进入本文的讲解~🏷️

📋文章内容抢先看

promise实现思维导图

📰一、js的同步模式和异步模式

1. 单线程💡

大家都知道, js 的设计是基于单线程进行开发的,它原先的目的在于只参与浏览器中DOM节点的操作。

而对于单线程来说,其意味着只能执行一个任务,且所有的任务都会按照队列的模式进行排队。

所以,单线程的缺点就在于,当 js 运行的时候, html 是不会进行渲染的。因此,如果一个任务特别耗时,那么将会很容易造成页面阻塞的局面。

为了解决这个问题, js 提出了同步模式异步模式的解决方案。

2. 同步模式💡

(1)定义

所谓同步模式,指的就是函数中的调用堆栈,按照代码实现的顺序,一步步进行。

(2)图例

接下来我们来用一段代码,演示 js函数调用堆栈的执行情况。具体代码如下:

const func1 = () => {
   
    func2();
    console.log(3);
}

const func2 = () => {
   
    func3();
    console.log(4);
}

const func3 = () => {
   
    console.log(5);
}

func1(); //5 4 3

看到这里,相信很多小伙伴已经在构思其具体的执行顺序。下面用一张图来展示执行效果:

调用堆栈

对于这个数据结构来说,它遵循后进先出的原则。因此,当 func1func2func3 依次放进调用栈后, 遵循后进先出原则 ,那么 func3 函数的内容会先被执行,之后是 func2 ,最后是 func1

因此,对于 js同步模式来说,就是类似于上述的函数调用堆栈。

3. 异步模式💡

(1)举例

当程序遇到网络请求或定时任务等问题时,这个时候会有一个等待时间。

假设一个定时器设置 10s ,如果放在同步任务里,同步任务会阻塞代码执行,我们会等待 10s 后才能看到我们想要的结果。1个定时器的等待时间可能还好,如果这个时候是100个定时器呢?我们总不能等待着 1000s 的时间就为了看到我们想要的结果吧,这几乎不太现实。

那么这个时候就需要异步,通过异步来让程序不阻塞代码执行灵活执行程序

(2)定义

对于同步模式来说,它只能自上而下地一行一行执行,一行一行进行解析。那与同步模式不同的是,异步模式是按照我们想要的结果进行输出,不会像同步模式一样产生阻塞,以达到让程序可控的效果。

(3)js如何实现异步

相对于同步模式来说,异步模式的结构更为复杂。除了调用栈之外, 它还有消息队列事件循环这两个额外的机制。所谓事件循环,也称为 event loop事件轮询。因为 js 是单线程的,且异步需要基于回调来实现,所以, event loop 就是异步回调的实现原理。

JS在程序中的执行遵循以下规则:

  • 从前到后,一行一行执行;
  • 如果某一行执行报错,则停止下面代码的执行;
  • 先把同步代码执行完,再执行异步。

一起来看一个实例:

console.log('Hi');

setTimeout(function cb1(){
   
    console.log('cb1'); //cb1 即callback回调函数
}, 5000);

console.log('Bye');

//打印顺序:
//Hi
//Bye
//cb1

从上例代码中可以看到, JS 是先执行同步代码,所以先打印 HiBye ,之后执行异步代码,打印出 cb1

以此代码为例,下面开始讲解 event loop 的过程。

(4)event loop过程

对于上面这段代码,执行过程如下图所示:

在这里插入图片描述

从上图中可以分析出这段代码的运行轨迹。首先 console.log('Hi') 是同步代码,直接执行并打印出 Hi 。接下来继续执行定时器 setTimeout定时器是异步代码,所以这个时候浏览器会将它交给 Web APIs 来处理这件事情,因此先把它放到 Web APIs 中,之后继续执行 console.log('Bye')console.log('Bye') 是同步代码,在调用堆栈 Call Stack 中执行,打印出 Bye

到这里,调用堆栈 Call Stack 里面的内容全部执行完毕,当调用堆栈的内容为空时,浏览器就会开始去 消息队列 Callback Queue 寻找下一个任务,此时消息队列就会去 Web API 里面寻找任务,遵循先进先出原则,找到了定时器,且定时器里面是回调函数 cb1 ,于是把回调函数 cb1 传入任务队列中,此时 Web API 也空了,任务队列里面的任务就会传入到调用堆栈里Call Stack 里执行,最终打印出 cb1

4. 回调函数💡

早期我们在解决异步问题的时候,基本上都是使用callback回调函数的形式 来调用的。形式如下:

//获取第一份数据
$.get(url1, (data1) => {
   
    console.log(data1);

    //获取第二份数据
    $.get(url2, (data2) => {
   
        console.log(data2);

        //获取第三份数据
        $.get(url3, (data3) => {
   
            console.log(data3);

            //还可以获取更多数据
        });
    });
});

从上述代码中可以看到,早期在调用数据的时候,都是一层套一层, callback 调用 callback ,仿佛深陷调用地狱一样,数据也被调用的非常乱七八糟的。所以,因为 callback 对开发如此不友好,也就有了后来的 promise 产生。

promiseCommonJS 社区最早提出,之后在2015年的时候, ES6 将其写进语言标准中,统一了它的用法,原生提供了 Promise 对象。 promise 的出现,告别了回调地狱时代,解决了回调地狱 callback hell 的问题。

那下面我们就来看看 Promise 的各种神奇用法~

📃二、Promise异步方案

1. Promise的三种状态📂

(1)Promise的三种状态

状态 含义
pending 等待状态,即在过程中,还没有结果。比如正在网络请求,或定时器没有到时间。
fulfilled 满足状态,即事件已经解决了,并且成功了;当我们主动回调了 fulfilled 时,就处于该状态,并且会回调 then 函数。
rejected 拒绝状态,即事件已经被拒绝了,也就是失败了;当我们主动回调了 reject 时,就处于该状态,并且会回调 catch 函数。

(2)状态解释

对于 Promise 来说,它是一个对象,用来表示一个异步任务在执行结束之后返回的结果,它有 3 种状态pendingfulfilledrejected其执行流程如下:

Promise的执行流程

如果一个异步任务处于 pending 状态时,那么表示这个 promise 中的异步函数还未执行完毕,此时处于等待状态。相反,如果 promise 中的异步函数执行完毕之后,那么它只会走向两个结果:

  • fulfilled ,表示成功
  • rejected ,表示失败

一旦最终状态从 pending 变化为 fulfilled 或者 rejected 后,状态就再也不可逆

所以,总结来讲,Promise 对象有以下两个特点:

  • promise 对象的状态不受外界影响,一旦状态被唤起之后,函数就交由 web API 去处理,这个时候在函数主体中再执行任何操作都是没有用的
  • 只会出现 pendingfulfilled,或者 pendingrejected 状态,即要么成功要么失败。即使再对 promise 对象添加回调函数,也只会得到同样的结果,即它的状态都不会再发生被改变

2. 三种状态的变化和表现📂

(1)状态的变化

promise 主要有以上三种状态, pendingfulfilledrejected 。当返回一个 pending 状态的 promise 时,不会触发 thencatch 。当返回一个 fulfilled 状态时,会触发 then 回调函数。当返回一个 rejected 状态时,会触发 catch 回调函数。那在这几个状态之间,他们是怎么变化的呢?

1)演示1

先来看一段代码:

const p1 = new Promise((resolved, rejected) => {
   

});

console.log('p1', p1); //pending

在以上的这段代码中,控制台打印结果如下:

状态变化演示1

在这段代码中, p1 函数里面没有内容可以执行,所以一直在等待状态,因此是 pending

2)演示2

const p2 = new Promise((resolved, rejected) => {
   
    setTimeout(() => {
   
        resolved();
    });
});

console.log('p2', p2); //pending 一开始打印时
setTimeout(() => console.log('p2-setTimeout', p2)); //fulfilled

在以上的这段代码中,控制台打印结果如下:

状态变化演示2

在这段代码中, p2 一开始打印的是 pending 状态,因为它没有执行到 setTimeout 里面。等到后续执行 setTimeout 时,才会触发到 resolved 函数,触发后返回一个 fulfilled 状态 promise

3)演示3

const p3 = new Promise((resolved, rejected) => {
   
    setTimeout(() => {
   
        rejected();
    });
});

console.log('p3', p3);
setTimeout(() => console.log('p3-setTimeout', p3)); //rejected

在以上的这段代码中,控制台打印结果如下。

状态变化演示3

在这段代码中, p3 一开始打印的是 pending 状态,因为它没有执行到 setTimeout 里面。等到后续执行 setTimeout 时,同样地,会触发到 rejected 函数,触发后返回一个 rejected 状态的 promise

看完 promise 状态的变化后,相信大家对 promise 的三种状态分别在什么时候触发会有一定的了解。那么我们接下来继续看 promise 状态的表现。

(2)状态的表现

  • pending 状态,不会触发 thencatch
  • fulfilled 状态,会触发后续的 then 回调函数。
  • rejected 状态,会触发后续的 catch 回调函数。

我们来演示一下。

1)演示1

const p1 = Promise.resolve(100); //fulfilled
console.log('p1', p1);
p1.then(data => {
   
    console.log('data', data);
}).catch(err => {
   
    console.error('err', err);
});

在以上的这段代码中,控制台打印结果如下:

状态的表现演示1

在这段代码中, p1 调用 promise 中的 resolved 回调函数,此时执行时, p1 属于 fulfilled 状态, fulfilled 状态下,只会触发 .then 回调函数,不会触发 .catch ,所以最终打印出 data 100

2)演示2

const p2 = Promise.reject('404'); //rejected
console.log('p2', p2);
p2.then(data => {
   
    console.log('data2', data);
}).catch(err => {
   
    console.log('err2', err);
})

在以上的这段代码中,控制台打印结果如下:

状态的表现演示2

在这段代码中, p2 调用 promise 中的 reject 回调函数,此时执行时, p1 属于 reject 状态, reject 状态下,只会触发 .catch 回调函数,不会触发 .then ,所以最终打印出 err2 404

3. Promise的使用案例📂

对三种状态有了基础了解之后,我们用一个案例来精进对 Promise 的使用。现在,我们想要实现的功能是,通过 fs 模块,异步地调用本地的文件。如果文件存在,那么在控制台上输出文件的内容;如果文件不存在,则将抛出异常。实现代码如下:

const fs = require('fs');

const readFile = (filename) => {
   
    // 返回一个 promise 实例,以供 then 调用
    const promise = new Promise(function(resolve, reject){
   
        // 使用 readFile 去异步地读取文件,异步调用也是 promise 函数的意义
        // 注意:下面这个函数的逻辑是错误优先,也就是先err,再data
        fs.readFile(filename, (err, data) => {
   
            // 如果文件读取失败,就调取 reject ,并抛出异常
            if(err){
   
                reject(err);
            }else{
   
                // 如果成功,就调取 resolve ,并返回调用成功的数据
                resolve(data);
            }
        });
    });
    return promise;
}

// 测试代码
// 文件存在逻辑
const existedFile = readFile('./test.txt');
existedFile.then(
    (data) => {
   
        // Buffer.from()方法用于创建包含指定字符串,数组或缓冲区的新缓冲区。
        // Buffer.from(data).toString()读出文件里面的内容。文件里面记得写内容!!
        console.log('content: ', Buffer.from(data).toString());
    },
    (error) => {
   
        console.log(error);
    }
)

// 文件不存在逻辑
const failFile = readFile('./fail.txt');
failFile.then(
    (data) => {
   
        console.log(Buffer.from(data).toString());
    },
    (err) => {
   
        console.log(err);
    }
);

最终控制台的打印结果如下:

[Error: ENOENT: no such file or directory, open 'C:\\promise\\fail.txt'] {
   
  errno: -4058,
  code: 'ENOENT',
  syscall: 'open',
  path: 'C:\\promise\\fail.txt'
}
content:  这是一个测试文件!

大家可以看到,当 ./test.txt 文件存在时,那么 existedFile 会去调用后续的 .then 回调函数,因此最终返回调用成功的结果。注意,这是一个测试文件! 这行字就是 test 文件里面的内容。

同时, ./fail.txt 文件不存在,因此 failFile 会调用后续的 .catch 文件,同时将异常抛出。

现在,大家应该对 promise 的使用有了一定的了解,下面我们继续看 promisethencatch 对状态的影响。

4. then和catch对状态的影响📂

  • then 正常返回 fulfilled ,里面有报错则返回 rejected
  • catch 正常返回 fulfilled ,里面有报错则返回 rejected

我们先来看第一条规则: then 正常返回 fulfilled ,里面有报错则返回 rejected

1)演示1

const p1 = Promise.resolve().then(() => {
   
    return 100;
})
console.log('p1', p1); //fulfilled状态,会触发后续的.then回调
p1.then(() => {
   
    console.log('123');
});

在以上的这段代码中,控制台打印结果如下。

then和catch对状态的影响演示1

在这段代码中, p1 调用 promise 中的 resolve 回调函数,此时执行时, p1 正常返回 fulfilled , 不报错,所以最终打印出 123

2)演示2

const p2 = Promise.resolve().then(() => {
   
    throw new Error('then error');
});
console.log('p2', p2); //rejected状态,触发后续.catch回调
p2.then(() => {
   
    console.log('456');
}).catch(err => {
   
    console.error('err404', err);
});

在以上的这段代码中,控制台打印结果如下。

then和catch对状态的影响演示2

在这段代码中, p2 调用 promise 中的 resolve 回调函数,此时执行时, p2 在执行过程中,抛出了一个 Error ,所以,里面有报错则返回 rejected 状态 , 所以最终打印出 err404 Error: then error 的结果。

我们再来看第二条规则catch 正常返回 fulfilled ,里面有报错则返回 rejected

1)演示1(需特别谨慎! !)

const p3 = Promise.reject('my error').catch(err => {
   
    console.error(err);
});
console.log('p3', p3); //fulfilled状态,注意!触发后续.then回调
p3.then(() => {
   
    console.log(100);
});

在以上的这段代码中,控制台打印结果如下。

then和catch对状态的影响演示3

在这段代码中, p3 调用 promise 中的 rejected 回调函数,此时执行时, p3 在执行过程中,正常返回了一个 Error这个点需要特别谨慎!!这看起来似乎有点违背常理,但对于 promise 来说,不管时调用 resolved 还是 rejected ,只要是正常返回而没有抛出异常,都是返回 fulfilled 状态。所以,最终 p3 的状态是 fulfilled 状态,且因为是 fulfilled 状态,之后还可以继续调用 .then 函数。

2)演示2

const p4 = Promise.reject('my error').catch(err => {
   
    throw new Error('catch err');
});
console.log('p4', p4); //rejected状态,触发.catch回调函数
p4.then(() => {
   
    console.log(200);
}).catch(() => {
   
    console.log('some err');
});

在以上的这段代码中,控制台打印结果如下。

then和catch对状态的影响演示4

在这段代码中, p4 依然调用 promise 中的 reject 回调函数,此时执行时, p4 在执行过程中,抛出了一个 Error ,所以,里面有报错则返回 rejected 状态 , 此时 p4 的状态为 rejected ,之后触发后续的 .catch 回调函数。所以最终打印出 some err 的结果。

5. Promise的并行执行📂

(1)Promise.all

Promise.all 方法用于将多个 Promise 实例包装成一个新的 Promise 实例。比如:

var p = Promise.all([p1, p2, p3]);

p的状态由 p1p2p3 决定,分成两种情况:

  • 只有 p1p2p3 的状态都变为 fulfilled ,最终 p 的状态才会变为 fulfilled 。此时 p1p2p3 的返回值组成一个数组,并返回给 p 回调函数。
  • 只要 p1p2p3 这三个参数中有任何一个被 rejected , 那么 p 的状态就会变成 rejected 。此时第一个被 rejected 的实例的返回值将会返回给 p 的回调函数。

下面用一个实例来展示 Promise.all 的使用方式。具体代码如下:

//生成一个Promise对象的数组
var promises = [4, 8, 16, 74, 25].map(function (id) {
   
    return getJSON('/post/' + id + ".json");
});

Promise.all(promises).then(fucntion (posts) {
   
    // ...
}).catch(function (reason) {
   
    // ...       
}}

大家可以看到,对于以上代码来说, promises 是包含5个Promise实例的数组,只有这5个实例的状态都变成 fulfilled ,或者其中有一个变为 rejected ,那么才会调用 Promise.all 方法后面的回调函数。


这里有一种值得注意的特殊情况是,如果作为参数的 Promise 实例自身定义了 catch 方法,那么它被 rejected 时并不会触发 Promise.all()catch 方法。这样说可能比较抽象,我们用一个实例来展示一下,具体代码如下:

const p1 = new Promise((resolve, reject) => {
   
    resolve('hello');
}).then(result => {
   
    return result;
}).catch(e => {
   
    return e;
});

const p2 = new Promise((resolve, reject) => {
   
    throw new Error('报错了');
}).then(result => {
   
    return result;
}).catch(e => {
   
    return e;
});

Promise.all([p1, p2]).then(result => {
   
    console.log(result);
}).catch(e => {
   
    console.log(e);
})

在上面的代码中, p1resolve ,之后调用后续的 .then 回调函数。而 p2reject ,因此之后会调用后续的 .catch 回调函数。注意,这里的 p2 有自己的 catch 方法,且该方法返回的时一个新的 Promise 实例,而 p2 实际上指向的就是这个实例。

所以呢,这个实例执行完 catch 方法后也会变成 resolved 。因此, 在 Promise.all() 这个方法中,其参数里面的两个实例就都会 resolved ,所以之后会调用 then 方法指定的回调函数,而不会调用 catch 方法指定的回调函数。

(2)Promise.race

Promise.race 方法同样是将多个 Promise 实例包装成一个新的 Promise 实例。比如:

var p = Promise.race([p1, p2, p3]);

我们同样用以上这段代码来进行分析。与 Promise.all() 不同的是,只要 p1p2p3 中有一个实例率先改变状态,那么** p 的状态就会跟着改变**,且那个率先改变的 Promise 实例的返回值就会传递给 p 的回调函数。

所以呀,为什么它叫 racerace ,顾名思义就是竞赛的意思。在赛场上,第一名永远只有一个。而我们可以把第一名视为第一个 resolve 状态的 promise ,只要第一名出现了,那么结果就是第一名赢了,所以返回的值就是第一个为 resolve 的值。其他人再怎么赛跑都逃不过拿不到第一的现实。

6. 两个有用的附加方法📂

ES6Promise API 并没有提供很多方法,但是我们可以自己来部署一些有用的方法。接下来,我们将来部署两个不在 ES6 中但是却很有用的方法。

(1)done()

无论 Promise 对象的回调链以 then 方法还是 catch 方法结尾,只要最后一个方法抛出错误,那么都有可能出现无法捕捉到的情况。这是为什么呢?原因在于 promise 内部的错误并不会冒泡到全局。因此,我们提供了一个 done 方法。done 方法总是处于回调链的尾端,保证抛出任何可能出现的错误。我们来看下它的使用方式,具体代码如下:

asyncFunc()
    .then(f1)
    .catch(r1)
    .then(f2)
    .done();

同时呢,它的实现代码也比较简单,我们来看一下。具体代码如下:

Promise.prototype.done = function (onFulfilled, onRejected) {
   
    this.then(onFulfilled, onRejected) 
        .catch(function (reason) {
   
            //抛出一个全局错误
            setTimeout(() => {
   
                throw reason;
            }, 0);
        })
}

由以上代码可知, done 方法可以像 then 方法那样使用,提供 fulfilledrejected 状态的回调函数,也可以不提供任何参数。但是不管如何, done 方法都会捕捉到任何可能出现的错误,并向全局抛出。

(2)finally()

finally 方法用于指定不管 Promise 对象最后状态如何都会执行的操作。它与 done 方法最大的区别在于,它接受一个普通的回调函数作为参数,该函数不管怎样都必须执行。

下面来展示一个例子。假设我们现在有一台服务器,现在让这台服务器使用 Promise 来处理请求,然后使用 finally 方法关掉服务器。具体实现代码如下:

server.listen(0)
    .then(function () {
   
    // run test
}).finally(server.stop);

同样地,它的实现代码也比较简单,我们来看一下。具体代码如下:

Promise.prototype.finally = function (callback) {
   
    let p = this.constructor;
    return this.then(
        value => p.resolve(callback()).then(() => {
   
            return value;
        }),
        reason => p.resolve(callback()).then(() => {
   
            throw reason;
        })
    );
};

通过以上代码我们可以了解到,不管前面的 promisefulfilled 还是 rejected ,最终都会执行回调函数 callback

📑三、实现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 也是按照这个模式去实现,这里不再进行讲解~

📝四、结束语

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

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

最后,本文讲解到这里就结束啦!希望大家能对 promise 有一个更好的了解~

🐣彩蛋 One More Thing

(:课代表记录

✅《三 7.》的 Promise.race 方法未用代码在原文中实现。

✅《三 8.》中,据资料调查, promise.resolve 还有第4种传参方式,当参数是一个 thenable ,即参数是一个带有 then 方法的对象时,则结果返回具有 then 方法的对象(本功能暂未实现)。

✅《三 8.》的 Promise.reject 方法未用代码在原文中实现。

(:参考资料

👉 [万字详解]JavaScript 中的异步模式及 Promise 使用

👉 [1w6k 字详细讲解] 保姆级一步一步带你实现 Promise 的核心功能

👉 ES6快速入门

(:番外篇

  • 关注公众号星期一研究室,第一时间关注优质文章,更多精选专栏待你解锁~
  • 如果这篇文章对你有用,记得留个脚印jio再走哦~
  • 以上就是本文的全部内容!我们下期见!👋👋👋
相关文章
|
6月前
|
前端开发 JavaScript
用原生JavaScript(ES5)来实现Promise的等效功能(异步回调)
用原生JavaScript(ES5)来实现Promise的等效功能(异步回调)
|
存储 移动开发 JavaScript
带你读《现代Javascript高级教程》二十六、JS中的异步编程与Promise(1)
带你读《现代Javascript高级教程》二十六、JS中的异步编程与Promise(1)
|
前端开发 JavaScript
带你读《现代Javascript高级教程》二十六、JS中的异步编程与Promise(2)
带你读《现代Javascript高级教程》二十六、JS中的异步编程与Promise(2)
|
存储 前端开发 JavaScript
带你读《现代Javascript高级教程》二十七、实现符合Promise/A+规范的Promise(1)
带你读《现代Javascript高级教程》二十七、实现符合Promise/A+规范的Promise(1)
|
前端开发 JavaScript
带你读《现代Javascript高级教程》二十七、实现符合Promise/A+规范的Promise(2)
带你读《现代Javascript高级教程》二十七、实现符合Promise/A+规范的Promise(2)
|
前端开发 JavaScript
带你读《现代Javascript高级教程》二十七、实现符合Promise/A+规范的Promise(3)
带你读《现代Javascript高级教程》二十七、实现符合Promise/A+规范的Promise(3)
|
资源调度 前端开发 JavaScript
带你读《现代Javascript高级教程》二十七、实现符合Promise/A+规范的Promise(4)
带你读《现代Javascript高级教程》二十七、实现符合Promise/A+规范的Promise(4)
|
前端开发 JavaScript 测试技术
带你读《现代Javascript高级教程》二十七、实现符合Promise/A+规范的Promise(5)
带你读《现代Javascript高级教程》二十七、实现符合Promise/A+规范的Promise(5)
|
前端开发 JavaScript
带你读《现代Javascript高级教程》二十七、实现符合Promise/A+规范的Promise(6)
带你读《现代Javascript高级教程》二十七、实现符合Promise/A+规范的Promise(6)
|
存储 移动开发 前端开发
《现代Javascript高级教程》JavaScript中的异步编程与Promise
JS中的异步编程与Promise 一、JavaScript的异步编步机制 在了解JavaScript的异步机制之前,我们首先需要理解JavaScript是一种单线程语言。单线程就意味着所有的任务需要按照顺序一次执行,如果前一个任务没有完成,后一个任务就无法开始。这个特性在执行大量或耗时任务时可能会导致阻塞或者界面卡死,这显然是不可取的。
90 1

热门文章

最新文章