promise的使用场景
- 使用promise能够有效的解决js异步回调地狱问题
- 能够将业务逻辑与数据处理分隔开使代码更优雅,方便阅读,更有利于代码维护
如何创建自己的MyPromise, 首先要理解你日常开发中是怎么使用原生Promise的,
首先就是创建MyPromise
一:
场景:
使用hello world => Promise
var p = new MyPromise((resolve,reject)=>{
if("成功"){
resolve("value")
}else {
reject("reason")
}
})
这时就可以开始创建最简单的MyPromise了。
class MyPromise {
constructor(executor) {
this.status = 'pending' //状态值
this.value = undefined //成功的返回值
this.reason = undefined //失败的返回值
// 成功
let resolve = (value) => {
if (this.status == 'pending') {
this.status = 'fullFilled'
this.value = value
}
}
// 失败
let reject = (reason) => {
if (this.status == 'pending') {
this.status = 'rejected'
this.reason = reason
}
}
try {
// 执行函数
executor(resolve, reject)
} catch (err) {
// 失败则直接执行reject函数
reject(err)
}
}
}
必须牢记Promise最重要的三个变量:
status('pending','fullFilled','rejected'),value ,reason
status
控制流程走异步还是同步,取决于state是'pending',还是 'fullFilled' 和 'rejected',
'fullFilled'则走成功的回调函数, 'rejected'则走失败的回调函数;
value
理论上,value可以是任何数据类型。
成功时,它存储的是 resolve("value"),或者 then的第一个回调函数的return值。
reason (同上)
理论上,value可以是任何数据类型。
失败时,它存储的是 reject("reason"),或者 then的第二个回调函数的return值。
二:
再换个情景:
var p = new MyPromise((resolve, reject) => {
if ("成功") {
resolve("value")
} else {
reject("reason")
}
}).then((res) => {
console.log(res)
})
如果我们需要使用 .then 链式调用。 这可是Promise最大的卖点,怎么可能少了它呢?
补充代码,如下:
class MyPromise {
constructor(executor) {
this.status = 'pending' //状态值
this.value = undefined //成功的返回值
this.reason = undefined //失败的返回值
// 成功
let resolve = (value) => {
if (this.status == 'pending') {
this.status = 'fullFilled'
this.value = value
}
}
// 失败
let reject = (reason) => {
if (this.status == 'pending') {
this.status = 'rejected'
this.reason = reason
}
}
try {
// 执行函数
executor(resolve, reject)
} catch (err) {
// 失败则直接执行reject函数
reject(err)
}
}
then(onFullFilled, onRejected) {
// 状态为fulfuilled,执行onFullFilled,传入成功的值
if (this.status == 'fullFilled') {
onFullFilled(this.value)
}
// 状态为rejected,执行onRejected,传入失败的值
if (this.status == 'rejected') {
onRejected(this.reason)
}
}
}
三:
换个场景:
var p = new MyPromise((resolve, reject) => {
if ("成功") {
setTimeout(()=>{
resolve("value")
},3000)
} else {
reject("reason")
}
}).then((res) => {
console.log(res)
})
这时,程序没有任何反应。。。
分析原因: 在new MyPromise 的时候,会创建MyPromise实例,但由于设置了setTimeout,导致resolve("value")迟迟未执行,在创建MyPromise实例后马上调用原型链上的then方法,这时status还是'pending' ,所以程序没有任何反应。
小结:前面的步骤都是建立在最简单的层面上思考Promise, 都只是支持同步执行,那如果异步都不支持,Promise可以说是毫无价值, 因为随便的一个普通函数都支持同步操作,(那还要Promise何用?)
所以? 完善它: 如下代码:
class MyPromise {
constructor(executor) {
this.status = 'pending' //状态值
this.value = undefined //成功的返回值
this.reason = undefined //失败的返回值
this.onResolvedCallbacks = [] //成功的回调函数
this.onRejectedCallbacks = [] //失败的回调函数
// 成功
let resolve = (value) => {
// pending用来屏蔽的,resolve和reject只能调用一个,不能同时调用,这就是pending的作用
if (this.status == 'pending') {
this.status = 'fullFilled'
this.value = value
// 发布执行函数
this.onResolvedCallbacks.forEach(fn => fn())
}
}
// 失败
let reject = (reason) => {
if (this.status == 'pending') {
this.status = 'rejected'
this.reason = reason
//失败执行函数
this.onRejectedCallbacks.forEach(fn => fn())
}
}
try {
// 执行函数
executor(resolve, reject)
} catch (err) {
// 失败则直接执行reject函数
reject(err)
}
}
then(onFullFilled, onRejected) {
// 同步
if (this.status == 'fullFilled') {
onFullFilled(this.value)
}
if (this.status == 'rejected') {
onRejected(this.reason)
}
// 异步
if (this.status == 'pending') {
// 在pending状态的时候先订阅
this.onResolvedCallbacks.push(() => {
// todo
onFullFilled(this.value)
})
this.onRejectedCallbacks.push(() => {
// todo
onRejected(this.reason)
})
}
}
}
解决思路:既然异步的时候会先走then方法, 那就先把then里的回调函数放在数组里面缓存起来,等将来resolve被执行时,在forEach 逐个执行就好了。
这时,你可算是拥有了一个最简单且起码完善的Promise了,可以执行then回调函数,可以执行异步操作, 点赞!
四:
继续下面场景:
var p = new MyPromise((resolve, reject) => {
if ("成功") {
setTimeout(()=>{
resolve("value")
},3000)
} else {
reject("reason")
}
}).then((res) => {
console.log(res)
return res
}).then((res2) => {
console.log(res2)
})
出现了这个报错了吗?
是不是发现了自己的MyPromise不可以链式回调了?
原因是 在第一个then执行后,没有返回 MyPromise,(或者 其它数据类型),自然第二个的then无法执行了。
那赶紧撸起袖子补上来吧:
class MyPromise {
constructor(executor) {
this.status = 'pending' //状态值
this.value = undefined //成功的返回值
this.reason = undefined //失败的返回值
this.onResolvedCallbacks = [] //成功的回调函数
this.onRejectedCallbacks = [] //失败的回调函数
// 成功
let resolve = (value) => {
// pending用来屏蔽的,resolve和reject只能调用一个,不能同时调用,这就是pending的作用
if (this.status == 'pending') {
this.status = 'fullFilled'
this.value = value
// 发布执行函数
this.onResolvedCallbacks.forEach(fn => fn())
}
}
// 失败
let reject = (reason) => {
if (this.status == 'pending') {
this.status = 'rejected'
this.reason = reason
//失败执行函数
this.onRejectedCallbacks.forEach(fn => fn())
}
}
try {
// 执行函数
executor(resolve, reject)
} catch (err) {
// 失败则直接执行reject函数
reject(err)
}
}
then(onFullFilled, onRejected) {
// 这样就是一个递归
let promise2 = new MyPromise((resolve, reject) => {
// 函数里面调函数就跟第一次使用一样,主要的是这里面的this指向怎么变化的
// 同步
let x
if (this.status == 'fullFilled') {
// 箭头函数,无论this一直是指向最外层的对象
x = onFullFilled(this.value)
resolve(x) // resolve(x) // 这一步x只能处理普通值,但是x可能是一个函数对象,或者promise,所以要对x进行判断
// 添加一个resolvePromise()的方法来判断x跟promise2的状态,决定promise2是走成功还是失败
}
if (this.status == 'rejected') {
x = onRejected(this.reason)
reject(x)
}
// 异步
if (this.status == 'pending') {
// 在pending状态的时候先订阅
this.onResolvedCallbacks.push(() => {
// todo
x = onFullFilled(this.value)
resolve(x)
})
this.onRejectedCallbacks.push(() => {
// todo
x = onRejected(this.reason)
resolve(x)
})
}
})
return promise2 //then方法返回一个promise对象
}
}
小结:本场景为了使用链式回调,实质上我们只是在原理的基础上,return了一个 promise2 ,和把then里的回调函数执行结果赋值给 x, 执行 resolve(x),把结果存进 promise2 的value 里,接下来就是无限重复而已(取决于你使用的多少个then).
五:
那如果我 return 还是返回Promise,或者其它的数据类型呢?
var p = new MyPromise((res) => {
setTimeout(() => res(1), 3000)
});
p.then((res) => {
return res + "2";
})
.then((data) => {
return new MyPromise((resolve, reject) => {
resolve(data + "3")
})
})
.then((result) => {
console.log("result", result)
})
直接贴代码:
const isFunction = (value) => typeof value === 'function'
const PENDING = 'pending'
const RESOLVED = 'fulFilled'
const REJECTED = 'rejected'
const resolvePromise = (promise2, x, resolve, reject) => {
// x和promise2不能是同一个人,如果是同一个人就报错
// 加一个开关,防止多次调用失败和成功,跟pending状态值一样的逻辑一样,走了失败就不能走成功了,走了成功一定不能在走失败
if (promise2 === x) {
return reject(
new TypeError('Chaining cycle detected for promise #<promise>')
)
}
// 判断如果x是否是一个对象,判断函数是否是对象的方法有:typeof instanceof constructor toString
if ((typeof x === 'object' && x != null) || typeof x === 'function') {
let called
try { // 预防取.then的时候错误
let then = x.then // Object.definePropertype
if (typeof then === 'function') {
// 用then.call()为了避免在使用一次x.then报错
then.call(x, y => {
// resolve(y)// 采用promise的成功结果,并且向下传递
if (called) {
return
}
called = true
// y有可能是一个promise,那么我们就要继续使用回调函数,直到解析出来的值是一个普通值
resolvePromise(promise2, y, resolve, reject)
}, r => {
if (called) {
return
}
called = true
reject(r)// 采用promise的失败结果,并且向下传递
})
} else {
if (called) {
return
}
called = true
resolve(x)// x不是一个函数,是一个对象
}
} catch (err) {
if (called) {
return
}
called = true
reject(err)
}
} else {
// x是一个普通值
resolve(x)
}
}
class MyPromise {
constructor(executor) {
this.status = PENDING
this.value = undefined
this.reason = undefined
this.onResolvedCallbacks = []
this.onRejectedCallbacks = []
// 成功
let resolve = (value) => {
// pending最屏蔽的,resolve和reject只能调用一个,不能同时调用,这就是pending的作用
if (this.status == PENDING) {
this.status = RESOLVED
this.value = value
// 发布执行函数
this.onResolvedCallbacks.forEach(fn => fn())
}
}
// 失败
let reject = (reason) => {
if (this.status == PENDING) {
this.status = REJECTED
this.reason = reason
this.onRejectedCallbacks.forEach(fn => fn())
}
}
try {
// 执行函数
executor(resolve, reject)
} catch (err) {
// 失败则直接执行reject函数
reject(err)
}
}
then(onFulFilled, onRejected) {
// onfulfilled, onrejected 都是可选参数
onFulFilled = isFunction(onFulFilled) ? onFulFilled : data => data
onRejected = isFunction(onRejected) ? onRejected : err => {
throw err
}
let promise2 = new MyPromise((resolve, reject) => {
// 箭头函数,无论this一直是指向最外层的对象
// 同步
if (this.status == RESOLVED) {
setTimeout(() => {
try {
let x = onFulFilled(this.value)
// 添加一个resolvePromise()的方法来判断x跟promise2的状态,决定promise2是走成功还是失败
resolvePromise(promise2, x, resolve, reject)
} catch (err) { // 中间任何一个环节报错都要走reject()
reject(err)
}
}, 0) // 同步无法使用promise2,所以借用setiTimeout异步的方式
// MDN 0>=4ms
}
if (this.status == REJECTED) {
setTimeout(() => {
try {
let x = onRejected(this.reason)
resolvePromise(promise2, x, resolve, reject)
} catch (err) { // 中间任何一个环节报错都要走reject()
reject(err)
}
}, 0) // 同步无法使用promise2,所以借用setiTimeout异步的方式
}
// 异步
if (this.status == PENDING) {
// 在pending状态的时候先订阅
this.onResolvedCallbacks.push(() => {
// todo
setTimeout(() => {
try {
let x = onFulFilled(this.value)
resolvePromise(promise2, x, resolve, reject)
} catch (err) { // 中间任何一个环节报错都要走reject()
reject(err)
}
}, 0) // 同步无法使用promise2,所以借用setiTimeout异步的方式
})
this.onRejectedCallbacks.push(() => {
// todo
setTimeout(() => {
try {
let x = onRejected(this.reason)
resolvePromise(promise2, x, resolve, reject)
} catch (err) { // 中间任何一个环节报错都要走reject()
reject(err)
}
}, 0) // 同步无法使用promise2,所以借用setiTimeout异步的方式
})
}
})
return promise2
}
}
小结:
resolvePromise方法主要用来判断x的值,如果x的值是一个普通的值,就直接返回x的值,如果x的值是一个promise,就要返回x.then() 执行的结果。
本结可能需要一定的阅读代码水平,由于本人讲解水平有限,就偷了个懒,靠读者自行摸索阅读。
本章也是参考下面文章整理出自己的代码思路。