大家好,我是指针。冬天到了,人也变懒了,为了让自己动起来,我报名参加了拉钩教育的大前端高薪训练营。学习需要总结,需要分享,需要鞭策,于是便有了《针爱学前端》这一系列,希望大家看完能够有收获。如果文章中有不对的地方,希望能批评指正,不吝赐教!!!
0.亲身经历的呼吁
大家用谷歌,用谷歌,用谷歌!!!360极速浏览器,class属性写在构造函数外面会报错,耽误了我好久,没想过是浏览器的问题,最后经过老师的提醒才发现,太坑人了!!!
1.例行叨叨叨
新年将近,我送给大家一副对联吧。
上联:金三银四,手写,算法,面试造火箭
下联:朝九晚五,框架,类库,工作拧螺丝
横批:我爱大厂
不开玩笑了,这些都是我自愿的,我自愿手写Promise,完全是出于真爱,我要是撒谎,我以后买键盘没有cv键/(ㄒoㄒ)/~~
2.如何手写Promise
- 首先,先把我上一期的文章阅读全文并背诵
- 其次,明白Promise有哪些功能
- 最后,干就完事了嗷XDM,奥里给!
3.开始手写
1.Promise首先是个类,因为它可以实例化,我们使用es6提供的class来模拟它,当然构造函数也行,但是写的太烦了,算了算了
2.Promise接受一个立即执行的函数,这个函数又接受两个参数,一个resolve,一个reject
3.Promise包含三种状态,初始状态等待(pending),成功状态(fulFilled),失败状态(rejected)
4.Promise的状态由resolve和reject改变
第一步,咱们就先完成上面的四点吧
// 用常量定义三个状态,避免单词写错的小尴尬事件
const PENDING = "pending";
const FULFILLED = "fulfilled";
const REJECTED = "rejected";
class MyPromise {
constructor (executor) {
executor(this.resolve, this.reject)
}
status = PENDING
resolve = () => {
this.status = FULFILLED
}
reject = () => {
this.status = REJECTED
}
}
5.Promise的状态一经改变,就不能再变化,状态已经锁定了
6.Promise的then方法接受两个函数作为参数,一个成功回调函数,一个失败回调函数,两个函数也接受一个参数,分别表示成功的结果和失败的原因,这两个值由resolve和reject分别传递过来
// 声明了两个参数value和reason来保存成功和失败的结果
// 方便then方法的成功与失败回调使用
// 而Promise的状态改变之后便锁定也很容易实现,在改变status之前判断status是否等于pending
// 如果status !== pending,则代表status已经发生了改变,直接return
class MyPromise {
constructor (executor) {
executor(this.resolve, this.reject)
}
status = PENDING
// 成功的值
value = undefined
// 失败的原因
reason = undefined
resolve = value => {
if (this.status !== PENDING) return
this.status = FULFILLED
this.value = value
}
reject = reason => {
if (this.status !== PENDING) return
this.status = REJECTED
this.reason = reason
}
then = (successCallback, failCallback) => {
if (this.status === FULFILLED) {
successCallback(this.value)
} else {
failCallback(this.reason)
}
}
}
let p = new MyPromise((res, rej) => {
console.log(111);
res("success")
rej("fail")
})
// 只会执行成功回调
p.then(value => {
console.log(value);
}, err => {
console.log(err);
})
7.Promise是用来处理异步的,那么我们试着加入异步的逻辑,怎么加?如果执行到then时,status === pending,将then里的方法储存起来,在resolve或reject的最后执行就行啦,类似于回调函数,类似于哈,但不是,你就这么想就行了
class MyPromise {
constructor (executor) {
executor(this.resolve, this.reject)
}
status = PENDING
// 成功的值
value = undefined
// 失败的原因
reason = undefined
// 新增了成功回调
successCallback = undefined
// 新增了失败回调
failCallback = undefined
resolve = value => {
if (this.status !== PENDING) return
this.status = FULFILLED
this.value = value
// 执行异步逻辑
this.successCallback && this.successCallback(this.value)
}
reject = reason => {
if (this.status !== PENDING) return
this.status = REJECTED
this.reason = reason
// 执行异步逻辑
this.failCallback && this.failCallback(this.reason)
}
then = (successCallback, failCallback) => {
if (this.status === FULFILLED) {
successCallback(this.value)
} else if (this.status === REJECTED) {
failCallback(this.reason)
} else {
// 此时status为pending,说明处于异步情况,我们需要将成功回调和失败回调存储起来
// 在resolve()或reject()最后的地方执行
this.successCallback = successCallback
this.failCallback = failCallback
}
}
}
// 首先then执行了,但是因为res在两秒后才会执行,所以then里的status===pending
// 所以then里的回调不会执行,而是先储存起来
// 2秒后,res执行,成功回调在res的最后执行
// 这就是Promise通过then执行异步逻辑的原因,将同步代码保存起来,放在异步代码的最后执行
let p = new MyPromise((res, rej) => {
setTimeout(() => {
res("success")
}, 2000)
})
p.then(value => {
console.log(value);
}, err => {
console.log(err);
})
8.Promise的then方法可以多次调用,每次调用的方法可能都不相同,所以我们需要将所有的成功回调与失败回调保存起来全部保存起来,在status改变之后,遍历调用
当然同步代码不影响,因为直接执行了,异步代码因为是保存着的,所以现在我们要用一个数组给他包起来了
class MyPromise {
constructor (executor) {
executor(this.resolve, this.reject)
}
status = PENDING
// 成功的值
value = undefined
// 失败的原因
reason = undefined
// 新增成功回调集合
successCallbackList = []
// 新增失败回调集合
failCallbackList = []
resolve = value => {
if (this.status !== PENDING) return
this.status = FULFILLED
this.value = value
// 执行异步逻辑
// this.successCallback && this.successCallback(this.value)
// 数组的shift方法是将截取数组第一个,并返回截取的那个,会改变原数组,如果理解不了的可以for循环
while (this.successCallbackList.length) this.successCallbackList.shift()(this.value)
}
reject = reason => {
if (this.status !== PENDING) return
this.status = REJECTED
this.reason = reason
// 执行异步逻辑
// this.failCallback && this.failCallback(this.reason)
while (this.failCallbackList.length) this.failCallbackList.shift()(this.value)
}
then = (successCallback, failCallback) => {
if (this.status === FULFILLED) {
successCallback(this.value)
} else if (this.status === REJECTED) {
failCallback(this.reason)
} else {
// 将回调函数保存在数组里,遍历执行
this.successCallbackList.push(successCallback)
this.failCallbackList.push(failCallback)
}
}
}
let p = new MyPromise((res, rej) => {
setTimeout(() => {
res("success")
}, 2000)
})
p.then(value => {
console.log(value);
}, err => {
console.log(err);
})
p.then(value => {
console.log(222);
}, err => {
console.log(err);
})
p.then(value => {
console.log(333);
}, err => {
console.log(err);
})
9.Promise的链式调用,是解决回调地狱的关键。你知道为啥能链式调用吗?现在我们知道Promise对象可以执行.then,所以每次.then过后就返回一个新的Promise就行了,这点我是在函数式编程的函子那里得到的思路,没看过的,看我以前的文章函数式编程吧
10..then(success, fail)返回一个Promise,定为promise2,该Promise的状态由success或者fail的返回值value来决定。这里value分为两种,一种是值返回,会触发promise2的res(value);另一种value是一个Promise对象,定为promise3,我们就要执行该promise3.then(),如果promise3的成功或失败回调还是一个Promise对象,则再次执行,直到返回的是一个值
class MyPromise {
constructor (executor) {
executor(this.resolve, this.reject)
}
status = PENDING
// 成功的值
value = undefined
// 失败的原因
reason = undefined
// 成功回调集合
successCallbackList = []
// 失败回调集合
failCallbackList = []
resolve = value => {
if (this.status !== PENDING) return
this.status = FULFILLED
this.value = value
// 执行异步逻辑
// 数组的shift方法是将截取数组第一个,并返回截取的那个,会改变原数组,如果理解不了的可以for循环
while (this.successCallbackList.length) this.successCallbackList.shift()(this.value)
}
reject = reason => {
if (this.status !== PENDING) return
this.status = REJECTED
this.reason = reason
// 执行异步逻辑
while (this.failCallbackList.length) this.failCallbackList.shift()(this.reason)
}
then = (successCallback, failCallback) => {
// 这里实例化了一个promise2,并且返回它,以供后面链式调用
let promise2 = new MyPromise((resolve, reject) => {
if (this.status === FULFILLED) {
let result = successCallback(this.value)
// 判断result的类型
judgeResult(result, resolve, reject)
} else if (this.status === REJECTED) {
let result = failCallback(this.reason)
// 判断result的类型
judgeResult(result, resolve, reject)
} else {
this.successCallbackList.push(() => {
let result = successCallback(this.value)
// 判断result的类型
judgeResult(result, resolve, reject)
})
this.failCallbackList.push(() => {
let result = failCallback(this.reason)
// 判断result的类型
judgeResult(result, resolve, reject)
})
}
})
return promise2
}
}
/**
* 判断result,如果是个值,那就直接resolve
* 如果是个Promise,那需要执行.then(),根据.then的结果返回
*/
function judgeResult(result, resolve, reject) {
if (result instanceof MyPromise) {
result.then(value => resolve(value), error => reject(error))
} else {
resolve(result)
}
}
p.then(value => {
console.log(value);
return "p1--success"
}).then(value => {
console.log(value);
return new MyPromise((res, rej) => {
rej("p2--fail")
})
}).then(undefined, err => {
console.log(err);
return new MyPromise((res, rej) => {
setTimeout(()=> {
res("p3--async")
}, 1000)
})
}).then(value => {
console.log(value);
})
11.then返回的如果是个Promise,那么这个返回不能是自己,否则就死循环了,这里也要处理一下
class MyPromise {
constructor (executor) {
executor(this.resolve, this.reject)
}
status = PENDING
// 成功的值
value = undefined
// 失败的原因
reason = undefined
// 成功回调集合
successCallbackList = []
// 失败回调集合
failCallbackList = []
resolve = value => {
if (this.status !== PENDING) return
this.status = FULFILLED
this.value = value
// 执行异步逻辑
// 数组的shift方法是将截取数组第一个,并返回截取的那个,会改变原数组,如果理解不了的可以for循环
while (this.successCallbackList.length) this.successCallbackList.shift()()
}
reject = reason => {
if (this.status !== PENDING) return
this.status = REJECTED
this.reason = reason
// 执行异步逻辑
while (this.failCallbackList.length) this.failCallbackList.shift()()
}
then = (successCallback, failCallback) => {
// 这里实例化了一个promise2,并且返回它,以供后面链式调用
let promise2 = new MyPromise((resolve, reject) => {
if (this.status === FULFILLED) {
// 这里为了判断自己有没有返回自己,将所有处理都延时操作了,只有这样,才能取到promise2
setTimeout(() => {
let result = successCallback(this.value)
// 判断result的类型
judgeResult(promise2, result, resolve, reject)
}, 0)
} else if (this.status === REJECTED) {
// 这里为了判断自己有没有返回自己,将所有处理都延时操作了,只有这样,才能取到promise2
setTimeout(() => {
let result = failCallback(this.reason)
// 判断result的类型
judgeResult(promise2, result, resolve, reject)
}, 0)
} else {
this.successCallbackList.push(() => {
// 这里为了判断自己有没有返回自己,将所有处理都延时操作了,只有这样,才能取到promise2
setTimeout(() => {
let result = successCallback(this.value)
// 判断result的类型
judgeResult(promise2, result, resolve, reject)
}, 0)
})
this.failCallbackList.push(() => {
// 这里为了判断自己有没有返回自己,将所有处理都延时操作了,只有这样,才能取到promise2
setTimeout(() => {
let result = failCallback(this.reason)
// 判断result的类型
judgeResult(promise2, result, resolve, reject)
}, 0)
})
}
})
return promise2
}
}
/**
* 判断result,如果是个值,那就直接resolve
* 如果是个Promise,那需要执行.then(),根据.then的结果返回
*/
function judgeResult(promise2, result, resolve, reject) {
if(promise2 === result) {
return reject("别自己返回自己啊喂!!!")
}
if (result instanceof MyPromise) {
result.then(value => resolve(value), error => reject(error))
} else {
resolve(result)
}
}
p.then(value => {
console.log(value);
return "p1--success"
}).then(value => {
console.log(value);
return new MyPromise((res, rej) => {
rej("p2--fail")
})
}).then(undefined, err => {
console.log(err);
return new MyPromise((res, rej) => {
setTimeout(()=> {
res("p3--async")
}, 1000)
})
}).then(value => {
console.log(value);
})
// 测试不能返回自己
let p2 = p.then((value) => {
console.log(value);
return p2
})
p2.then((value)=> {
console.log(value)
}, (error) => {
console.log(error)
})
12.then方法的参数不是一个函数时,会自动转换成一个value => value的方法
13.Promise中,每个地方产生的错,最终都会被捕获,所以现在把该捕获错误的地方都捕一捕
// 如何捕获失败,用try/catch,在哪里捕获?在你认为会错的地方😀
class MyPromise {
constructor (executor) {
try {
executor(this.resolve, this.reject)
} catch (error) {
this.reject(error)
}
}
status = PENDING
// 成功的值
value = undefined
// 失败的原因
reason = undefined
// 成功回调集合
successCallbackList = []
// 失败回调集合
failCallbackList = []
resolve = value => {
if (this.status !== PENDING) return
this.status = FULFILLED
this.value = value
// 执行异步逻辑
// 数组的shift方法是将截取数组第一个,并返回截取的那个,会改变原数组,如果理解不了的可以for循环
while (this.successCallbackList.length) this.successCallbackList.shift()()
}
reject = reason => {
if (this.status !== PENDING) return
this.status = REJECTED
this.reason = reason
// 执行异步逻辑
while (this.failCallbackList.length) this.failCallbackList.shift()()
}
then = (successCallback, failCallback) => {
// 判断successCallback和failCallback是不是函数,如果不是,转化成一个函数
successCallback = typeof successCallback === 'function' ? successCallback : value => value
failCallback = typeof failCallback === 'function' ? failCallback : value => value
let promise2 = new MyPromise((resolve, reject) => {
if (this.status === FULFILLED) {
try {
setTimeout(() => {
let result = successCallback(this.value)
// 判断result的类型
judgeResult(result, resolve, reject)
}, 0)
} catch (error) {
reject(error)
}
} else if (this.status === REJECTED) {
try {
setTimeout(() => {
let result = failCallback(this.reason)
// 判断result的类型
judgeResult(result, resolve, reject)
}, 0)
} catch (error) {
reject(error)
}
} else {
this.successCallbackList.push(() => {
try {
setTimeout(() => {
let result = successCallback(this.value)
// 判断result的类型
judgeResult(result, resolve, reject)
}, 0)
} catch (error) {
reject(error)
}
})
this.failCallbackList.push(() => {
try {
setTimeout(() => {
let result = failCallback(this.reason)
// 判断result的类型
judgeResult(result, resolve, reject)
}, 0)
} catch (error) {
reject(error)
}
})
}
})
return promise2
}
}
/**
* 判断result,如果是个值,那就直接resolve
* 如果是个Promise,那需要执行.then(),根据.then的结果返回
*/
function judgeResult(promise2, result, resolve, reject) {
throw(new Error("judgeResult error"))
if(promise2 === result) {
return reject("别自己返回自己啊喂!!!")
}
if (result instanceof MyPromise) {
result.then(value => resolve(value), error => reject(error))
} else {
resolve(result)
}
}
let p = new MyPromise((res, rej) => {
res("success")
})
p.then().then("b").then(value => console.log(value))
14.Promise包含几个重要的静态方法,就是我在上一章列出的几个,现在我们实现一下
// Promise.all 故名思意,就是等全部,全部成功之后,返回一个Promise对象,成功回调全部返回的数组
// 如果有失败,则会返回一个Promise对象,失败回调第一个失败的返回
// 之前采用push,经过好心人提醒,异步会导致返回的结果与原来的Promise对象数组的顺序不一致
// 改为普通的for循环,使用一个count计数,count == len时,便res(arr)
static all(array) {
try {
if (!(array instanceof Array)) throw(new Error("请传入一个数组好吗"))
if (array.length == 0) throw(new Error("请不要传入一个空数组好吗"))
let arr = []
let len = array.length
let count = 0;
function addAndJudgeAll(index, value, res) {
arr[index] = value
count++
if (count == len) res(arr)
}
return new MyPromise((res, rej) => {
for(let i = 0; i < len; i++) {
if (array[i] instanceof MyPromise) {
array[i].then(value=> {
addAndJudgeAll(i, value, res)
}, error => {
rej(error)
})
} else {
addAndJudgeAll(i, array[i], res)
}
}
})
} catch (error) {
console.log(error);
}
}
// Promise.race故名思意,竞争,看谁先结束,返回的Promise就用谁的返回
static race(array) {
try {
if (!(array instanceof Array)) throw(new Error("请传入一个数组好吗"))
if (array.length == 0) throw(new Error("请不要传入一个空数组好吗"))
return new MyPromise((res, rej) => {
for(let item of array) {
if (item instanceof MyPromise) {
item.then(value=> {
res(value)
}, error => {
rej(error)
})
} else {
res(item)
}
}
})
} catch (error) {
console.log(error);
}
}
// Promise.resolve 参数如果不是Promise对象,直接返回一个成功回调为参数的Promise对象
// 如果参数是Promise对象,那直接返回参数即可
static resolve(params) {
try {
if(params instanceof MyPromise) {
return params
} else {
return new MyPromise(res => {
res(params)
})
}
} catch (error) {
console.log(error);
}
}
15.最后还有.catch和.finally方法
// .catch就是一个没有成功回调的then
static catch(failCallback) {
return this.then(null, failCallback)
}
// .finally
// 回调函数中不包含当前Promise的返回值,也就是callback不传递参数
// 但是会把当前Promise的返回值return出去,所以调用this.then,获取当前Promise的返回值
// 如果finally返回一个Promise,会执行,但是仍然返回当前Promise的返回值,而不是fianlly返回的Promise的返回值
static finally(callback) {
this.then(value => {
this.resolve(callback()).then(() => value)
}, error => {
this.resolve(callback()).then(() => {
throw error
})
})
}
p1.then(()=> {
return "1111"
}).finally(value => {
console.log(value); // undefined
return new MyPromise(res => {
res("2222")
})
}).then(value=> {
console.log(value); // "1111"
})
总结
终于写完了,写的过程又看了一遍,练了一遍。我自己看着觉得还是挺满意的,希望能够给大家一点帮助吧。一时看不懂,或者记不住不要紧,多练多写,可以按照我的提纲,一步步完善,循序渐进,总有一天,会全部掌握的,未来的大神们。
导航
针要学前端 | JavaScript深度挖掘之手写Promise
针要学前端 | JavaScript深度挖掘之ECMAScript
参考
以上皆由拉钩教育大前端训练营提供材料😀