📚序言
众所周知, 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
看到这里,相信很多小伙伴已经在构思其具体的执行顺序。下面用一张图来展示执行效果:
对于栈这个数据结构来说,它遵循后进先出的原则。因此,当 func1
, func2
, func3
依次放进调用栈后, 遵循后进先出原则 ,那么 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
是先执行同步代码,所以先打印 Hi
和 Bye
,之后执行异步代码,打印出 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
产生。
promise
由 CommonJS
社区最早提出,之后在2015年的时候, ES6
将其写进语言标准中,统一了它的用法,原生提供了 Promise
对象。 promise
的出现,告别了回调地狱时代,解决了回调地狱 callback hell
的问题。
那下面我们就来看看 Promise
的各种神奇用法~
📃二、Promise异步方案
1. Promise的三种状态📂
(1)Promise的三种状态
状态 | 含义 |
---|---|
pending | 等待状态,即在过程中,还没有结果。比如正在网络请求,或定时器没有到时间。 |
fulfilled | 满足状态,即事件已经解决了,并且成功了;当我们主动回调了 fulfilled 时,就处于该状态,并且会回调 then 函数。 |
rejected | 拒绝状态,即事件已经被拒绝了,也就是失败了;当我们主动回调了 reject 时,就处于该状态,并且会回调 catch 函数。 |
(2)状态解释
对于 Promise
来说,它是一个对象,用来表示一个异步任务在执行结束之后返回的结果,它有 3 种状态: pending
, fulfilled
, rejected
。其执行流程如下:
如果一个异步任务处于 pending
状态时,那么表示这个 promise
中的异步函数还未执行完毕,此时处于等待状态。相反,如果 promise
中的异步函数执行完毕之后,那么它只会走向两个结果:
fulfilled
,表示成功;rejected
,表示失败。
一旦最终状态从 pending
变化为 fulfilled
或者 rejected
后,状态就再也不可逆。
所以,总结来讲,Promise
对象有以下两个特点:
promise
对象的状态不受外界影响,一旦状态被唤起之后,函数就交由web API
去处理,这个时候在函数主体中再执行任何操作都是没有用的;- 只会出现
pending
→fulfilled
,或者pending
→rejected
状态,即要么成功要么失败。即使再对promise
对象添加回调函数,也只会得到同样的结果,即它的状态都不会再发生被改变。
2. 三种状态的变化和表现📂
(1)状态的变化
promise
主要有以上三种状态, pending
、 fulfilled
和 rejected
。当返回一个 pending
状态的 promise
时,不会触发 then
和 catch
。当返回一个 fulfilled
状态时,会触发 then
回调函数。当返回一个 rejected
状态时,会触发 catch
回调函数。那在这几个状态之间,他们是怎么变化的呢?
1)演示1
先来看一段代码:
const p1 = new Promise((resolved, rejected) => {
});
console.log('p1', p1); //pending
在以上的这段代码中,控制台打印结果如下:
在这段代码中, p1
函数里面没有内容可以执行,所以一直在等待状态,因此是 pending
。
2)演示2
const p2 = new Promise((resolved, rejected) => {
setTimeout(() => {
resolved();
});
});
console.log('p2', p2); //pending 一开始打印时
setTimeout(() => console.log('p2-setTimeout', p2)); //fulfilled
在以上的这段代码中,控制台打印结果如下:
在这段代码中, 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
在以上的这段代码中,控制台打印结果如下。
在这段代码中, p3
一开始打印的是 pending
状态,因为它没有执行到 setTimeout
里面。等到后续执行 setTimeout
时,同样地,会触发到 rejected
函数,触发后返回一个 rejected
状态的 promise
。
看完 promise
状态的变化后,相信大家对 promise
的三种状态分别在什么时候触发会有一定的了解。那么我们接下来继续看 promise
状态的表现。
(2)状态的表现
pending
状态,不会触发then
和catch
。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);
});
在以上的这段代码中,控制台打印结果如下:
在这段代码中, 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);
})
在以上的这段代码中,控制台打印结果如下:
在这段代码中, 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
的使用有了一定的了解,下面我们继续看 promise
中 then
和 catch
对状态的影响。
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');
});
在以上的这段代码中,控制台打印结果如下。
在这段代码中, 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);
});
在以上的这段代码中,控制台打印结果如下。
在这段代码中, 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);
});
在以上的这段代码中,控制台打印结果如下。
在这段代码中, 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');
});
在以上的这段代码中,控制台打印结果如下。
在这段代码中, 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的状态由 p1
、 p2
、 p3
决定,分成两种情况:
- 只有
p1
、p2
、p3
的状态都变为fulfilled
,最终p
的状态才会变为fulfilled
。此时p1
、p2
、p3
的返回值组成一个数组,并返回给p
回调函数。 - 只要
p1
、p2
、p3
这三个参数中有任何一个被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);
})
在上面的代码中, p1
会 resolve
,之后调用后续的 .then
回调函数。而 p2
会 reject
,因此之后会调用后续的 .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()
不同的是,只要 p1
、 p2
、 p3
中有一个实例率先改变状态,那么** p
的状态就会跟着改变**,且那个率先改变的 Promise 实例的返回值就会传递给 p
的回调函数。
所以呀,为什么它叫 race
? race
,顾名思义就是竞赛的意思。在赛场上,第一名永远只有一个。而我们可以把第一名视为第一个 resolve
状态的 promise
,只要第一名出现了,那么结果就是第一名赢了,所以返回的值就是第一个为 resolve
的值。其他人再怎么赛跑都逃不过拿不到第一的现实。
6. 两个有用的附加方法📂
ES6
中 Promise 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
方法那样使用,提供 fulfilled
和 rejected
状态的回调函数,也可以不提供任何参数。但是不管如何, 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;
})
);
};
通过以上代码我们可以了解到,不管前面的 promise
是 fulfilled
还是 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
对象的同时,需要传进去一个回调函数; - 这个回调函数又需要接收两个回调函数
resolve
和reject
,且用这两个回调函数来作为参数,之后呢, 当调用成功时,使用resolve
回调函数,而当调用失败时,使用reject
回调函数。 resolve
和reject
这两个回调函数都将会被用来修改promise
的状态,resolve
会把pending
状态修改为fulfilled
,而reject
将会把pending
状态修改为rejected
。同时,值得注意的是,一旦状态确定后,后续所有操作的状态将不会再被更改,即不可逆。
(3)Promise基础功能的实现
我们现在来实现 promise
的基本功能,该功能含有以下几个组成要素:
- 实现
PromiseMon
的基础结构,其中包含构造函数和状态; - 实现
resolve
和reject
功能,这里先实现状态从pending
到fulfilled
或rejected
的改变,其余状态间的改变暂未实现。
具体实现代码如下:
/**
* 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
在调用了 resolve
和 reject
方法之后,就该来触发后续的 .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
中被调用的大部分都是异步函数,比如 setTimeout
、 setInterval
等等。所以呢,我们现在要在 then
中添加异步的功能,来实现对异步逻辑进行操作。
在上面的 then
方法中,大家定位到 if……else if……
部分,上面所写的逻辑只有对状态为 fulfilled
和 rejected
时才进行判断,而没有对状态为 pending
时进行判断。
所以,当状态为 pending
时,意味着 PromiseMon
中的异步函数还没有执行完毕。这个时候,我们需要将 succesCB
和 failCB
这两个回调函数给先存到一个变量中去,等到后续异步内容结束后再进行调用。
(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
方法相对比较简单,只要直接调用successCB
或failCB
回调函数即可。 - 异步调用
then
方法。之前的属性successCB
和failCB
两个回调函数是存放为对象形式,因此,我们需要先优化我们的存储形式。优化完成之后,将所有的回调函数全部存放在一起,等到执行完毕之后再依次调用。
(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
对象里面,去处理内部使用的resolve
和reject
所返回的值,最终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)错误处理场景
到这里,我们对 pormise
的 then
方法基本实现的差不多。但是还有一个很重要但是又很容易被我们疏忽的问题就是,错误处理。现在,我们来分析一下可能会出现异常的常见场景:
- 构造器中的回调函数
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.all
和 promise.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再走哦~
- 以上就是本文的全部内容!我们下期见!👋👋👋