进阶学习8:手写Promise源码
Posted JIZQAQ
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了进阶学习8:手写Promise源码相关的知识,希望对你有一定的参考价值。
目录
不得不说,这部分老师讲的真的太流畅了…真的是一层一层递进讲的,从最开始实现了部分promise的基本原理,然后引入一条新的用法,再在原本代码基础上添加该用法的编写。所以记笔记的时候,我并没有完全按照课时分开记,而是一边敲源码一边记在备注里面了。
这导致了这部分可能笔记不是特别便于阅读,如果大家有什么不明白的,欢迎留言一起交流学习哈。
一、编写思路
在我后面的源码里面,每一块对应的部分都有更详细的注释,所以这边我就简单说一下自己手写Promise代码的思路。
首先我们了解到Promise有下面3个核心属性,于是先把这部分的代码编写出来。
1. Promise 就是一个类 在执行这个类的时候 需要传递一个执行器进去 执行器会立即执行
2. Promise 中有三种状态 分别为 成功 fulfilled 失败 rejected 等待 pending
pending -> fulfilled
pending -> rejected
一旦状态确定就不可更改3. resolve和reject函数是用来更改状态的
resolve: fulfilled
reject: rejected
然后了解到then方法,于是再把简单的then方法编写
4. then方法内部做的事情就判断状态 如果状态是成功 调用成功的回调函数 如果状态是失败 调用失败回调函数 then方法是被定义在原型对象中的
5. then成功回调有一个参数 表示成功之后的值 then失败回调有一个参数 表示失败后的原因
当中要注意如果我们promise里面传入的是异步的函数,.then()是需要等待我们异步的部分完成之后再做判断的。我在.then()函数代码那块有更详细的备注。
接下来完成下面这个功能点
6. 同一个promise对象下面的then方法是可以被调用多次的
7. then方法是可以被链式调用的, 后面then方法的回调函数拿到值的是上一个then方法的回调函数的返回值
编写完之后6、7功能点之后,需要注意处理一些异常报错,如:
1.promise对象的.then()中可以return一个promise对象,但是不能是它本身。需要在我们程序中把这个行为识别出来,报指定的错误。
2.要捕获执行器当中发生的错误,并报错
还有.then()当中不传递任何东西的使用方式,实际上.then()的写法逻辑上等同于 .then(value => value)
最后依次实现以下功能点
8. promise.all()允许我们按照异步代码调用的顺序得到异步代码的执行结果,接收数组作为参数,里面可以是普通值也可以是promise对象,返回值也是一个promise对象;里面所有都成功才算成功,只要有一个任务失败就算失败
9. promise.resolve()里面可以给普通值或者promise对象,如果是promise对象会原封不动作为返回值,如果是普通值会包裹一层成为promise对象再传递下去,再后面用then方法来获得返回值
10.finally() 无论最后是成功还是失败,finally方法都会被执行一次,finally返回的时候promise,后面可以链式调用.then()获取值
11.catch() 用来处理当前promise对象最终为失败的情况的,then方法不传递失败回调的话,传了catch,那么失败方法会被catch捕获
二、源码
下面就直接晒出我的源码了。
MyPromise.js文件
/*
1. Promise 就是一个类 在执行这个类的时候 需要传递一个执行器进去 执行器会立即执行
2. Promise 中有三种状态 分别为 成功 fulfilled 失败 rejected 等待 pending
pending -> fulfilled
pending -> rejected
一旦状态确定就不可更改
3. resolve和reject函数是用来更改状态的
resolve: fulfilled
reject: rejected
4. then方法内部做的事情就判断状态 如果状态是成功 调用成功的回调函数 如果状态是失败 调用失败回调函数 then方法是被定义在原型对象中的
5. then成功回调有一个参数 表示成功之后的值 then失败回调有一个参数 表示失败后的原因
6. 同一个promise对象下面的then方法是可以被调用多次的
7. then方法是可以被链式调用的, 后面then方法的回调函数拿到值的是上一个then方法的回调函数的返回值
8. promise.all()允许我们按照异步代码调用的顺序得到异步代码的执行结果,接收数组作为参数,里面可以是普通值也可以是promise对象,返回值也是一个promise对象;里面所有都成功才算成功,只要有一个任务失败就算失败
9. promise.resolve()里面可以给普通值或者promise对象,如果是promise对象会原封不动作为返回值,如果是普通值会包裹一层成为promise对象再传递下去,再后面用then方法来获得返回值
10.finally() 无论最后是成功还是失败,finally方法都会被执行一次,finally返回的时候promise,后面可以链式调用.then()获取值
11.catch() 用来处理当前promise对象最终为失败的情况的,then方法不传递失败回调的话,传了catch,那么失败方法会被catch捕获
*/
const PENDING = 'pending'//等待
const FULFILLED = 'fulfilled'//成功
const REJECTED = 'rejected'//失败
class MyPromise {
//执行器
constructor (executor) {
try{
//执行器在这边立即执行,所以立刻调用它
executor(this.resolve, this.reject)
}catch(error){
this.reject(error)
}
}
//状态
status = PENDING
//成功之后的值
value = undefined
//失败之后的原因
reason = undefined
//成功回调
// successCallBack = undefined
successCallBack = []
// //失败回调
// failCallBack = undefined
failCallBack = []
//用箭头函数是为了让它内部的this指定不发生改变
resolve = value => {
//如果状态不是等待,阻止状态更改
if(this.status !== PENDING) return;
this.status = FULFILLED
// 保存成功之后的值,then方法需要用到
this.value = value
// 判断成功回调是否存在,如果存在就调用
// if(this.successCallBack !== undefined){
// this.successCallBack(this.value)
// }
while(this.successCallBack.length){
this.successCallBack.shift()(this.value)
}
}
reject = reason => {
if(this.status !== PENDING) return;
this.status = REJECTED
// 保存失败之后的原因,then方法需要用到
this.reason = reason
// 判断失败回调是否存在,如果存在就调用
// if(this.failCallBack !== undefined){
// this.failCallBack(this.reason)
// }
while(this.failCallBack.length){
this.failCallBack.shift()(this.reason)
}
}
then (successCallBack, failCallBack) {
//如果前面的.then()括号里面为空,那么默认是.then(value => value),同样错误也是这么传递
successCallBack = successCallBack??(value => value)
failCallBack = failCallBack??(reason => {throw reason})
let promise2 = new MyPromise((resolve, reject) => {
// 判断状态
if (this.status === FULFILLED){
//这里添加setTimeout是为了让下面这块变成异步代码,promise2需要在完全new完之后才能或得到
//如果不改成异步的话,resolvePromise无法获得promise2,所以延迟时间设置为0
setTimeout(()=>{
try{
let returnValue = successCallBack(this.value)
/*
* 需要判断 returnValue 是普通值还是promise对象
* 如果是普通值,直接调用resolve
* 如果是promise对象 查看promise对象返回的结果
* 再根据promise对象返回的结果决定调用resolve还是reject
* 无论是哪个状态都需要做这个判断,所以我们直接写成一个函数
*/
resolvePromise(promise2,returnValue,resolve, reject)
}catch(err){
reject(err)
}
},0)
}
else if (this.status === REJECTED){
setTimeout(()=>{
try{
let returnValue = failCallBack(this.reason)
/*
* 需要判断 returnValue 是普通值还是promise对象
* 如果是普通值,直接调用resolve
* 如果是promise对象 查看promise对象返回的结果
* 再根据promise对象返回的结果决定调用resolve还是reject
* 无论是哪个状态都需要做这个判断,所以我们直接写成一个函数
*/
resolvePromise(promise2,returnValue,resolve, reject)
}catch(err){
reject(err)
}
},0)
}
//处理等待状态,也就是promise里面被添加了异步逻辑
else{
//等待的话,将成功和失败回调存起来
// this.successCallBack = successCallBack
// this.failCallBack = failCallBack
this.successCallBack.push(() =>{
setTimeout(()=>{
try{
let returnValue = successCallBack(this.value)
/*
* 需要判断 returnValue 是普通值还是promise对象
* 如果是普通值,直接调用resolve
* 如果是promise对象 查看promise对象返回的结果
* 再根据promise对象返回的结果决定调用resolve还是reject
* 无论是哪个状态都需要做这个判断,所以我们直接写成一个函数
*/
resolvePromise(promise2,returnValue,resolve, reject)
}catch(err){
reject(err)
}
},0)
})
this.failCallBack.push(() =>{
setTimeout(()=>{
try{
let returnValue = failCallBack(this.reason)
/*
* 需要判断 returnValue 是普通值还是promise对象
* 如果是普通值,直接调用resolve
* 如果是promise对象 查看promise对象返回的结果
* 再根据promise对象返回的结果决定调用resolve还是reject
* 无论是哪个状态都需要做这个判断,所以我们直接写成一个函数
*/
resolvePromise(promise2,returnValue,resolve, reject)
}catch(err){
reject(err)
}
},0)
})
}
});
return promise2;
}
static all (array) {
let result = [];
let index = 0;
return new MyPromise((resolve,reject) => {
function addData (key, value) {
result[key] = value
index++
//只有index和array长度相等了,才证明所有异步操作执行完了。用这个来等待异步代码的结果
if(index === array.length){
resolve(result)
}
}
for(let i = 0;i<array.length;i++){
let current = array[i]
if(current instanceof MyPromise) {
//promise对象
current.then(value => addData(i, value),reason => reject(reason))
}else{
//普通值
addData(i, array[i])
}
}
})
}
static resolve (value) {
if(value instanceof MyPromise){
return value;
}
else{
return new MyPromise(resolve => resolve(value))
}
}
finally(callback) {
//现在情况是如果我们在finally里面return了一个promise对象,异步的,并没有等待就执行完了,所以需要做一些修改
return this.then(value =>{
//无论是我们callback返回的是普通值还是promise都转成promise
return MyPromise.resolve(callback()).then(() => value);
// callback()
// return value
},reason =>{
return MyPromise.resolve(callback()).then(() => {throw reason});
// callback()
// throw reason
})
}
catch(failCallBack){
return this.then(undefined,failCallBack)
}
}
function resolvePromise(promise2, returnValue,resolve, reject) {
if(promise2 === returnValue) {
return reject(new TypeError('Chaining cycle detected for promise #<Promise>'))
}
if (returnValue instanceof MyPromise){
//是promise对象
// returnValue.then(value=>{
// resolve(value)
// },reason=>{
// reject(reason)
// })
//简化上面写的代码为
returnValue.then(resolve,reject);
}else{
// 普通值
resolve(returnValue)
}
}
module.exports = MyPromise
调用的index.js文件 这块因为是每个模块MyPromise代码敲完之后测试的,忘记做具体备注了,大家可以对照上面那个文件开头列的每一条想一个对应的测试样例,这个并不麻烦,就当做是普通的Promise函数使用就是了。
import MyPromise from './MyPromise'
let promise = new MyPromise((resolve, reject) => {
//resolve('成功')
reject('失败')
//setTimeout(()=>resolve('成功...'),2000)
//throw new Error('executor error')
})
// promise.then(value => {
// console.log(value)
// }, reason => {
// console.log(reason)
// })
// promise.then(value => {
// console.log(value)
// }, reason => {
// console.log(reason)
// })
// promise.then(value => {
// console.log(value)
// }, reason => {
// console.log(reason)
// })
function other () {
return new MyPromise((resolve, reject) => {
//resolve('other');
reject('other');
})
}
// promise.then(value => {
// console.log(value)
// return 100;
// },reason => {
// console.log(reason)
// return 100;
// }).then(value => {
// console.log(value)
// return other()
// },reason => {
// console.log(reason)
// return other()
// }).then(value => {
// console.log(value)
// },reason => {
// console.log(reason)
// })
// let p1 = promise.then(value => {
// console.log(value)
// return p1;
// })
// p1.then(value=>{
// console.log(value);
// },reason => {
// console.log(reason.message)
// })
// promise.then(value=>{
// console.log(value);
// //throw new Error('executor error')
// return 'aaa'
// },reason => {
// console.log(value)
// return 10000
// }).then(value=>{
// console.log(value);
// //throw new Error('executor error')
// },reason => {
// console.log(reason.message)
// })
//promise.then(value => console.log(value))
// promise.then().then().then(value => console.log(value), reason => {
// console.log(reason)
// })
function p1 () {
return new MyPromise(function (resolve, reject) {
setTimeout(function () {
resolve('p1')},2000)
})
}
// function p2 () {
// return new MyPromise(function (resolve, reject) {
// resolve('p2 resolve')
// //reject('p2 reject')
// })
// }
//MyPromise.all(['a','b',p1(),p2(),'c']).then(result => console.log(result))
//MyPromise.resolve(100).then(value => console.log(value))
//MyPromise.resolve(p1()).then(value => console.log(value))
//这个例子现在是先打印finally,然后等待2秒打印p2 resolve,p1并不会被打印出来
// p2().finally(() => {
// console.log('finally')
// return p1()
// }).then(value=>{
// console.log(value)
// },reason => {
// console.log(reason)
// })
function p2 () {
return new MyPromise(function (resolve, reject) {
//resolve('p2 resolve')
reject('p2 reject')
})
}
p2().then(value => console.log(value)).catch(reason => console.log(reason))
三、运行方式
我使用到了webpack-dev-server运行的,如果之前没用使用过这个工具的,可以看一下我下面这篇学习笔记里面的五、Promise——4.使用Promise封装ajax函数 部分,里面有从最初的安装开始的讲解步骤。
这篇学习笔记也比较详细的讲了Promise的相关概念、属性、运行方式等等。
进阶学习6:javascript异步编程——Promise、链式调用、异常处理、静态方法、并行执行、执行时序、宏任务微任务理解
然后在Chrome打开对应的地址,F12观察console的输出是否和预期相符就好。
参考:拉钩网《大前端训练营》课程
以上是关于进阶学习8:手写Promise源码的主要内容,如果未能解决你的问题,请参考以下文章