保姆级详解promise的核心功能
- 📚序言
- 📋文章内容抢先看
- 📰一、js的同步模式和异步模式
- 1. 单线程💡
- 2. 同步模式💡
- (1)定义
- (2)图例
- 3. 异步模式💡
- (1)举例
- (2)定义
- (3)js如何实现异步
- (4)event loop过程
- 4. 回调函数💡
- 📃二、Promise异步方案
- 1. Promise的三种状态📂
- (1)Promise的三种状态
- (2)状态解释
- 2. 三种状态的变化和表现📂
- (1)状态的变化
- (2)状态的表现
- 3. Promise的使用案例📂
- 4. then和catch对状态的影响📂
- 5. Promise的并行执行📂
- (1)Promise.all
- (2)Promise.race
- 6. 两个有用的附加方法📂
- (1)done()
- (2)finally()
- 📑三、实现Promise的核心功能
- 1. 基础核心功能实现🏷️
- (1)碎碎念
- (2)Promise基础功能的分析
- (3)Promise基础功能的实现
- (4)thenable功能的分析
- (5)thenable功能的实现
- 2. 添加异步逻辑功能实现🏷️
- (1)then中添加异步逻辑功能的分析
- (2)then中添加异步逻辑功能的实现
- 3. 实现then方法的多次调用🏷️
- (1)多次调用then方法的功能分析
- (2)多次调用then方法的功能实现
- 4. 实现then方法的链式调用🏷️
- (1)then方法链式调用的功能分析
- (2)then方法链式调用的功能实现
- (3)链式调用的自我检测
- 5. promise的错误处理🏷️
- (1)错误处理场景
- (2)错误处理功能实现
- 6. 实现then方法的参数可选🏷️
- (1)参数可选实现思路
- (2)参数可选功能实现
- 7. 实现Promise.all🏷️
- (1)Promise.all功能分析
- (2)Promise.all功能实现
- 8. 实现Promise.resolve🏷️
- (1)Promise.resolve功能分析
- (2)Promise.resolve功能实现
- 📝四、结束语
- 🐣彩蛋 One More Thing
- (:课代表记录
- (:参考资料
- (:番外篇
📚序言
众所周知, 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,再datafs.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中的状态,默认状态为pendingstatus = 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;// 将成功的值赋予给valuethis.value = value;}};// 修改参数,让reject接收失败后传来的原因reject = (reason) => {if(this.status != PENDING) {return;}else{this.status = REJECTED;// 将失败的原因赋予给reasonthis.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); // 成功状态:successDatasuccessPromise.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); // 失败状态:failDatafailPromise.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()); // 第二次调用成功: otherPromisereturn 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/catchif(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对象,将其作为参数返回一个promiseelse {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再走哦~
- 以上就是本文的全部内容!我们下期见!👋👋👋