尽量先学会使用Promise,再来看原理
#回顾下Promise基本特征
- promise 有三个状态:pending,resolve(也叫fulfilled) 以及 reject;
- new Promise时,需要传递一个执行器函数 excutor,并且立即同步执行执行器函数;
- excuotr 接受两个函数类型的参数,分别是resolve和reject
- promise默认的状态是pending状态
- resolve有个value值,保存成功状态的值
- reject 有reason值,保存失败状态的值
- promise状态只能从pending 到 resolve 或者pending 到 reject,状态只能改变一次
- promise只能通过then方法获取结果,then 接收两个参数,分别是成功回调onResolved和失败回调onRejected回调
- 如果调用then时,promise已经是成功状态,则执行onResolved,参数是value
- 如果调用then时,promise已经是失败状态,则执行onRejected,参数是reason
- 如果执行器中抛出异常,那么会把异常作为参数 传递给then的失败的回调onRejected
使用Promise有两种情况
1:先改变状态,保存值,然后再指定回调函数
2:先指定回调函数,再改变状态
show code:
<script>
new Promise((resolve, reject) => {
resolve('promise') // 1: 先改变状态(同步)
}).then((value) => { // 2:再指定回调函数
console.log(value)
}, reason => {
console.log(reason)
})
</script>
<script>
new Promise((resolve, reject) => {
setTimeout(() => {
resolve('promise') // 2: 后改变状态(异步)
})
}).then((value) => { // 1:先指定回调函数
console.log(value)
}, reason => {
console.log(reason)
})
</script>
对比下两者的区别
#搭建
先搭建台子,准备唱戏,如下
- 准备js文件,自定义Promise
- 准备html文件,引入上面的Promise
笔者目录如下:
html文件:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
//引入自定义promise文件
<script src="./../lib/Promise.js"></script>
<script>
// 测试代码
</script>
</body>
</html>
Promise.js文件
;(function (window) {
/**
* Promise构造函数
* @param {执行器函数} excutor(同步执行)
*/
function Promise(excutor) {
// 定义状态常量
const PENDING = 'pending'
const RESOLVE = 'resolve'
const REJECT = 'reject'
// 保存当前this
const self = this
self.data = undefined // 用来存放promise结果的属性
self.status = PENDING //指定promise对象的默认状态
self.callbacks = [] // 用来存放回调函数 ( 先指定回调函数,再改变状态) 数据结果:{onResolved(){},onJejectd(){}}
// 成功的promise 执行成功的回调函数
function resolve(value) {
// 如果当前状态不是pending状态 就结束
if (self.status !== PENDING) return
// 改变状态为resolve
self.status = RESOLVE
// 保存数据
self.data = value
// 如果有回调函数,则异步执行回调函数onResolved
if (self.callbacks.length > 0) {
// 放到队列中,保证异步执行
setTimeout(() => {
self.callbacks.forEach(({ onResolved }) => {
onResolved(value)
})
})
}
}
function reject(reason) {
// 如果当前状态不是pending状态 就结束
if (self.status !== PENDING) return
// 改变状态为reject
self.status = REJECT
// 保存数据
self.data = reason
// 如果有回调函数,则异步执行回调函数onRejected
if (self.callbacks.length > 0) {
// 放到队列中,保证异步执行
setTimeout(() => {
self.callbacks.forEach(({ onRejected }) => {
onRejected(reason)
})
})
}
}
try {
// 立即同步执行构造器函数
excutor(resolve, reject)
} catch (err) {
//如果捕获到执行器函数excutor抛出的异常 则改为失败状态
reject(err)
}
/**
* Promise原型对象上的then方法
* 指定成功和失败的回调函数
* 返回一个新的Promise对象
* @param {成功的回调} onResolved
* @param {失败的回调} onRejected
*/
Promise.prototype.then = function (onResolved, onRejected) {
// 如果当前状态是pending状态 则保存回调函数 等待执行
this.callbacks.push({
onResolved,
onRejected,
})
}
}
// 向外暴露Promise构造函数
window.Promise = Promise
})(window)
至此,我们就可以测试一下了
测试代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script src="./../lib/Promise.js"></script>
<script>
new Promise((resolve, reject) => {
setTimeout(() => {
resolve('这里测试手写Promise')
})
}).then((value) => {
console.log('成功回调onResolved:', value)
}, reason => {
console.log('失败回调onRejected:', reason)
})
</script>
</body>
</html>
控制台输出:
成功回调onResolved: 这里测试手写Promise
现在已经实现了其中一种情况: 先指定回调函数,再改变状态
如果我们改成另外一种情况:先改变状态,再指定回调函数来测试一下
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script src="./../lib/Promise.js"></script>
<script>
new Promise((resolve, reject) => {
resolve('这里测试手写Promise')
}).then((value) => {
console.log('成功回调onResolved:', value)
}, reason => {
console.log('失败回调onRejected:', reason)
})
</script>
</body>
</html>
控制台不会有任何输出,我们debug代码,会发现callbacks里面是空的,callbacks里面没有回调函数,所以就不会有任何输出
因为这里是先改状态 即先执行resolve('这里测试手写Promise') 再执行then()方法
任重而道远 接着撸
这也是最难的一步:
#完善then方法
首先得明白then方法干了些什么~
- 返回一个新的Promise
- 指定成功和失败的回调函数
- 如果当前promise状态是pending状态,则缓存回调函数
- 如果当前promise状态是resolve状态,则调用成功的回调函数onResolved
- 如果当前promise状态是reject状态,则调用成功的回调函数onRejected
注意:这里第四点和第五点还要进行剖析
先说第四点--回调函数onResolved的执行结果有三种情况
- 如果回调函数onResolved的执行结果抛出异常,则return的promise就会失败,失败的reason就是error
- 如果回调函数onResolved的执行结果不是promis,则return的promise就会成功,value就是返回的值
- 如果回调函数onResolved的执行结果是promise,则return的promise的结果就是当前promise的结果
- 当前promise的结果就是下面代码的const result = onResolved(this.data)的result的结果
- result是promise,又分为成功状态的promise和失败状态的promise
好绕口呀~
第五点和第四点的剖析是一样的,就不再赘述了
/**
* Promise原型对象上的then方法
* 指定成功和失败的回调函数
* 返回一个新的Promise对象
* @param {成功的回调} onResolved
* @param {失败的回调} onRejected
*/
Promise.prototype.then = function (onResolved, onRejected) {
const self = this
// 返回一个新的promise对象
return new Promise((resolve, reject) => {
if (self.status === PENDING) {
// 如果当前状态是pending状态 则保存回调函数 等待执行
self.callbacks.push({
onResolved,
onRejected,
})
} else if (self.status === RESOLVE) {
// 如果当前状态是resolve状态
// 异步执行成功回调函数
setTimeout(() => {
// 1: 如果执行抛出异常,return的promise就会失败,失败的reason就是error
try {
const result = onResolved(self.data)
// 3: 如果result是promise,return的promise的结果就是这个promise的结果
if (result instanceof Promise) {
result.then(
// 如果调用这个方法,则说明result是成功状态的promise,则返回成功状态的promise
(value) => resolve(value),
// 如果调用这个方法,则说明result是失败状态的promise,则返回失败状态的promise
(reason) => reject(reason),
)
} else {
// 2: 如果result不是promise return的promise就会成功,value就是返回的值
resolve(result)
}
} catch (error) {
// 捕获到异常就直接返回失败的promise
reject(error)
}
})
} else {
// 如果当前状态是reject状态
// 异步执行失败回调函数
setTimeout(() => {
onRejected(self.data)
})
}
})
}
当前状态是reject状态,和resolve状态代码基本一致,只是会调用失败的回调函数onRejected
/**
* Promise原型对象上的then方法
* 指定成功和失败的回调函数
* 返回一个新的Promise对象
* @param {成功的回调} onResolved
* @param {失败的回调} onRejected
*/
Promise.prototype.then = function (onResolved, onRejected) {
const self = this
// 返回一个新的promise对象
return new Promise((resolve, reject) => {
if (self.status === PENDING) {
// 如果当前状态是pending状态 则保存回调函数 等待执行
self.callbacks.push({
onResolved,
onRejected,
})
} else if (self.status === RESOLVE) {
// 如果当前状态是resolve状态
// 异步执行成功回调函数
setTimeout(() => {
// 1: 如果执行抛出异常,return的promise就会失败,失败的reason就是error
try {
const result = onResolved(self.data)
// 3: 如果result是promise,return的promise的结果就是这个promise的结果
if (result instanceof Promise) {
result.then(
// 如果调用这个方法,则说明result是成功状态的promise,则返回成功状态的promise
(value) => resolve(value),
// 如果调用这个方法,则说明result是失败状态的promise,则返回失败状态的promise
(reason) => reject(reason),
)
} else {
// 2: 如果result不是promise return的promise就会成功,value就是返回的值
resolve(result)
}
} catch (error) {
// 捕获到异常就直接返回失败的promise
reject(error)
}
})
} else {
// 如果当前状态是reject状态
// 异步执行失败回调函数
setTimeout(() => {
// 1: 如果执行抛出异常,return的promise就会失败,失败的reason就是error
try {
const result = onRejected(self.data)
// 3: 如果result是promise,return的promise的结果就是这个promise的结果
if (result instanceof Promise) {
result.then(
// 如果调用这个方法,则说明result是成功状态的promise,则返回成功状态的promise
(value) => resolve(value),
// 如果调用这个方法,则说明result是失败状态的promise,则返回失败状态的promise
(reason) => reject(reason),
)
} else {
// 2: 如果result不是promise return的promise就会成功,value就是返回的值
resolve(result)
}
} catch (error) {
// 捕获到异常就直接返回失败的promise
reject(error)
}
})
}
})
}
写到这儿,then方法就完成一半了
接着写另外一半
当状态是pending状态的时候,现在的操作是将回调函数存起来的,如果只是存起来,不执行结果就不会影响返回的promise结果
所以这里还要这样修改下
/**
* Promise原型对象上的then方法
* 指定成功和失败的回调函数
* 返回一个新的Promise对象
* @param {成功的回调} onResolved
* @param {失败的回调} onRejected
*/
Promise.prototype.then = function (onResolved, onRejected) {
// 返回一个新的promise对象
const self = this
return new Promise((resolve, reject) => {
if (self.status === PENDING) {
// 如果当前状态是pending状态 回调函数的执行结果就是当前promise的结果
self.callbacks.push({
onResolved() {
try {
const result = onResolved(self.data)
// 3: 如果result是promise,return的promise的结果就是这个promise的结果
if (result instanceof Promise) {
result.then(
// 如果调用这个方法,则说明result是成功状态的promise,则返回成功状态的promise
(value) => resolve(value),
// 如果调用这个方法,则说明result是失败状态的promise,则返回失败状态的promise
(reason) => reject(reason),
)
} else {
// 2: 如果result不是promise return的promise就会成功,value就是返回的值
resolve(result)
}
} catch (error) {
// 捕获到异常就直接返回失败的promise
reject(error)
}
},
onRejected() {
try {
const result = onRejected(self.data)
// 3: 如果result是promise,return的promise的结果就是这个promise的结果
if (result instanceof Promise) {
result.then(
// 如果调用这个方法,则说明result是成功状态的promise,则返回成功状态的promise
(value) => resolve(value),
// 如果调用这个方法,则说明result是失败状态的promise,则返回失败状态的promise
(reason) => reject(reason),
)
} else {
// 2: 如果result不是promise return的promise就会成功,value就是返回的值
resolve(result)
}
} catch (error) {
// 捕获到异常就直接返回失败的promise
reject(error)
}
},
})
} else if (self.status === RESOLVE) {
// 如果当前状态是resolve状态
// 异步执行成功回调函数
setTimeout(() => {
// 1: 如果执行抛出异常,return的promise就会失败,失败的reason就是error
try {
const result = onResolved(self.data)
// 3: 如果result是promise,return的promise的结果就是这个promise的结果
if (result instanceof Promise) {
result.then(
// 如果调用这个方法,则说明result是成功状态的promise,则返回成功状态的promise
(value) => resolve(value),
// 如果调用这个方法,则说明result是失败状态的promise,则返回失败状态的promise
(reason) => reject(reason),
)
} else {
// 2: 如果result不是promise return的promise就会成功,value就是返回的值
resolve(result)
}
} catch (error) {
// 捕获到异常就直接返回失败的promise
reject(error)
}
})
} else {
// 如果当前状态是reject状态
// 异步执行失败回调函数
setTimeout(() => {
// 1: 如果执行抛出异常,return的promise就会失败,失败的reason就是error
try {
const result = onRejected(self.data)
// 3: 如果result是promise,return的promise的结果就是这个promise的结果
if (result instanceof Promise) {
result.then(
// 如果调用这个方法,则说明result是成功状态的promise,则返回成功状态的promise
(value) => resolve(value),
// 如果调用这个方法,则说明result是失败状态的promise,则返回失败状态的promise
(reason) => reject(reason),
)
} else {
// 2: 如果result不是promise return的promise就会成功,value就是返回的值
resolve(result)
}
} catch (error) {
// 捕获到异常就直接返回失败的promise
reject(error)
}
})
}
})
}
当前代码笔记冗余,稍微封装下
提取公共函数handle 传入回调函数
/**
* Promise原型对象上的then方法
* 指定成功和失败的回调函数
* 返回一个新的Promise对象
* @param {成功的回调} onResolved
* @param {失败的回调} onRejected
*/
Promise.prototype.then = function (onResolved, onRejected) {
const self = this
// 返回一个新的promise对象
return new Promise((resolve, reject) => {
// 处理函数
function handle(callback) {
try {
const result = callback(self.data)
// 3: 如果result是promise,return的promise的结果就是这个promise的结果
if (result instanceof Promise) {
result.then(
// 如果调用这个方法,则说明result是成功状态的promise,则返回成功状态的promise
(value) => resolve(value),
// 如果调用这个方法,则说明result是失败状态的promise,则返回失败状态的promise
(reason) => reject(reason),
)
} else {
// 2: 如果result不是promise return的promise就会成功,value就是返回的值
resolve(result)
}
} catch (error) {
// 捕获到异常就直接返回失败的promise
reject(error)
}
}
if (self.status === PENDING) {
// 如果当前状态是pending状态 回调函数的执行结果就是当前promise的结果
self.callbacks.push({
onResolved() {
handle(onResolved)
},
onRejected() {
handle(onRejected)
},
})
} else if (self.status === RESOLVE) {
// 如果当前状态是resolve状态
// 异步执行成功回调函数
setTimeout(() => {
handle(onResolved)
})
} else {
// 如果当前状态是reject状态
// 异步执行失败回调函数
setTimeout(() => {
handle(onRejected)
})
}
})
}
写到这里就完成了90%了,继续~~但是可以先测试一下
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script src="./../lib/Promise.js"></script>
<script>
new Promise((resolve, reject) => {
resolve('这里测试手写Promise')
}).then((value) => {
console.log('成功回调onResolved:', value)
}, reason => {
console.log('失败回调onRejected:', reason)
})
</script>
</body>
</html>
控制台输出:
成功回调onResolved: 这里测试手写Promise
#错误穿透以及代码健壮性
- 如果onResolved不是函数,则默认向后传递数据value
- 如果onRejected不是函数,则向后传递失败的reason
// 向后传递成功的value
onResolved = typeof onResolved ? onResolved : (value) => value
// 指定默认的失败的回调(实现异常穿透的关键点) 向后传递失败的reasonm
onRejected = typeof onRejected === 'function' ? onRejected : (reason) => { throw reason }
#then方法完整代码
/**
* Promise原型对象上的then方法
* 指定成功和失败的回调函数
* 返回一个新的Promise对象
* @param {成功的回调} onResolved
* @param {失败的回调} onRejected
*/
Promise.prototype.then = function (onResolved, onRejected) {
// 向后传递成功的value
onResolved = typeof onResolved ? onResolved : (value) => value
// 指定默认的失败的回调(实现异常穿透的关键点) 向后传递失败的reasonm
onRejected =
typeof onRejected === 'function'
? onRejected
: (reason) => {
throw reason
}
const self = this
// 返回一个新的promise对象
return new Promise((resolve, reject) => {
// 处理函数 根据执行的结果,改变return的promise的状态
function handle(callback) {
try {
const result = callback(self.data)
// 3: 如果result是promise,return的promise的结果就是这个promise的结果
if (result instanceof Promise) {
result.then(
// 如果调用这个方法,则说明result是成功状态的promise,则返回成功状态的promise
(value) => resolve(value),
// 如果调用这个方法,则说明result是失败状态的promise,则返回失败状态的promise
(reason) => reject(reason),
)
} else {
// 2: 如果result不是promise return的promise就会成功,value就是返回的值
resolve(result)
}
} catch (error) {
// 捕获到异常就直接返回失败的promise
reject(error)
}
}
if (self.status === PENDING) {
// 如果当前状态是pending状态 回调函数的执行结果就是当前promise的结果
self.callbacks.push({
onResolved() {
handle(onResolved)
},
onRejected() {
handle(onRejected)
},
})
} else if (self.status === RESOLVE) {
// 如果当前状态是resolve状态
// 异步执行成功回调函数
setTimeout(() => {
handle(onResolved)
})
} else {
// 如果当前状态是reject状态
// 异步执行失败回调函数
setTimeout(() => {
handle(onRejected)
})
}
})
}
测试:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script src="./../lib/Promise.js"></script>
<script>
new Promise((resolve, reject) => {
resolve('这里测试手写Promise')
}).then((value) => {
console.log('成功回调onResolved:', value)
return 123
}, reason => {
console.log('失败回调onRejected:', reason)
}).then(value => {
console.log('onResolved1:', value)
}, reason => {
console.log('onRejected1:', reason)
})
</script>
</body>
</html>
浏览器输出:
#实现catch方法
Promise.prototype.catch = function (onRejected) {
return this.then(undefined, onRejected)
}
测试catch方法
<body>
<script src="./../lib/Promise.js"></script>
<script>
new Promise((resovle, reject) => {
reject(1)
}).then(value => {
console.log(value)
}, reason => {
console.log(reason) //输出 '1'
throw '这里测试catch'
}).catch(err => {
console.log(err) //输出 '这里测试catch'
})
</script>
</body>
浏览器输出:
#实现Promise.resolve静态方法
- resolve方法接收一个参数value
- value可以是promise对象,也可以不是promise对象
- 如果value是promise对象,则value的值就是返回的promise的值
- 如果value不是promise对象,则返回成功状态的promise
// Promise函数对象的 resolve 方法
//返回一个新的Promise对象,Promise.resolve()中可以传入Promise
Promise.resolve = function (value) {
// 返回一个新的promise
return new Promise((resolve, reject) => {
if (value instanceof Promise) {
// value如果是promise 则value的值就是promsie的值
// value是成功状态就调用resolve
// value是失败状态就调用reject
value.then(
(value) => resolve(value),
(reason) => reject(reason),
)
} else {
//value不是promise ==> promise变成成功,数据是value
resolve(value)
}
})
}
测试resolve方法
<body>
<script src="./../lib/Promise.js"></script>
<script>
// 传入非promise对象
const p1 = Promise.resolve(1)
// 传入成功状态的promise
const p2 = Promise.resolve(Promise.resolve(2))
// 传入失败状态的promise
const p3 = Promise.resolve(new Promise((resolve, reject) => {
reject(3)
}))
p1.then(value => {
console.log(value) // 输出 1
})
p2.then(value => {
console.log(value) // 输出 2
})
p3.then(value => {
console.log(value)
}, reason => {
console.log(reason) // 输出 3
})
</script>
</body>
浏览器输出:
#实现Promise.reject静态方法
- reject方法接收一个参数reason
- 返回一个失败的promise
// Promise函数对象的 reject 方法
//返回一个新的Promise对象 Promise.reject中不能再传入Promise
Promise.reject = function (reason) {
return new Promise((_, reject) => {
reject(reason)
})
}
测试reject方法
<body>
<script src="./../lib/Promise.js"></script>
<script>
const p1 = Promise.reject(1)
p1.then(value => {
}, reason => {
console.log(reason) // 输出 1
})
</script>
</body>
#实现Promise.all静态方法
- all方法接收一个参数,其类型是一个数组
- 只有当全部都返回成功时,才返回成功状态的promise,否则就返回失败的promise
// Promise函数对象的 all 方法,接受一个promise类型的数组
// 返回一个新的Promise对象
Promise.all = function (promises) {
// 保证返回的值得结果的顺序和传进来的时候一致
// 只有全部都成功长才返回成功
const values = new Array(promises.length)
let successCount = 0
return new Promise((resolve, reject) => {
promises.forEach((p, index) => {
// 如果p不是一个Promise对象用Promise.resolve包装下
Promise.resolve(p).then(
(value) => {
successCount++
// 用下标进行赋值,因为p是异步的
values[index] = value
// 只有当成功的次数等于传进来的Promises的长度时,才成功
if (successCount === promises.length) {
resolve(values)
}
},
(reason) => {
// 只要有一个失败,就返回失败状态的promise
reject(reason)
},
)
})
})
}
测试all方法
<body>
<script src="./../lib/Promise.js"></script>
<script>
const p1 = Promise.resolve(1)
const p2 = Promise.resolve(2)
const p3 = Promise.reject(3)
const params1 = [p1, p2]
const params2 = [p1, p2, p3]
const result = Promise.all(params1)
result.then(values => {
console.log(values) //输出 [1,2]
}, reason => {
console.log(reason)
})
const result1 = Promise.all(params2)
result1.then(value => {
console.log(value)
}, reason => {
console.log(reason) // 输出 3
})
</script>
</body>
浏览器输出:
#实现Promise.race静态方法
- 接收一个数组
- 只要给定的一个promise解决或拒绝,就采用第一个promise的值作为它的值
// Promise函数对象的 race 方法,接受一个promise类型的数组
// 返回一个新的Promise对象
Promise.race = function (promises) {
return new Promise((resolve, reject) => {
promises.forEach((p) => {
// 谁先返回就是返回谁~~~~~
Promise.resolve(p).then(
(value) => {
resolve(value)
},
(reason) => {
reject(reason)
},
)
})
})
}
测试race方法:
<body>
<script src="./../lib/Promise.js"></script>
<script>
const p1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(1)
}, 1000)
})
const p2 = new Promise((resolve, reject) => {
setTimeout(() => {
reject(2)
}, 100)
})
Promise.race([p1, p2]).then(value => {
console.log(value)
}, reason => {
console.log(reason) // p2先完成 所以输出 2
})
</script>
</body>
浏览器输出:
Promise原理撸完了
总结一下和普通回调函数的优缺点:
回调函数的弊端:
- 依赖前一次的执行结果,产生回调地狱,可读性和可维护性变差
- 由于异步,try...catch难以捕获到异步调用的异常
- 并行问题,无法知道某个异步操作是否已经完成,需要设置变量来辨别
Promise是异步编程的新的解决方案,链式调用,代码可读性强,也能轻松进行错误捕获,但Promise并不是异步编程最优解,最优解应该是它的语法糖async ....await