本文正在参加「金石计划 . 瓜分6万现金大奖」
前言
这是一篇讲解Promise的文章,相当于自己对Promise的再次学习与自我总结
请点击Script 食用 【看完文章再来看demo】
为什么要用Promise
-
在Promise 之前解决异步的方式就是回调函数,当有多个 回调函数的时候,就形成了我们经常说的 回调地狱
-
回调函数非常难维护,因此社区出现了很多替代的解决方案,其实就是 类似于 Promise的形式,在ES6中 正式 推出了 Promise,但,这次是原生的代码
-
Promise的好处是 链式调用,好维护,代码阅读性强
promise 的基本用法
基本代码示例
let p = new Promise((resolved,reject)=>{
resolved('成功')
})
三个状态
- 三个状态 pendding onFulfilled 和 onRejected
- 状态从 pendding 到 onFulfilled 或者 onRejected,一旦状态改变,不可逆
- 通过调用resolved(),来将状态 变成 onFulfilled,通过调用reject(),将状态变成onRejected
拒绝的情况可以分为两种
- 通过then方法的第二个函数
p.then(fun1,func2=>{此处是 拒绝状态的回调})let p8 = new Promise((resolve,reject)=>{ setTimeout(()=>{ // resolve('成功 == p8') reject('失败 == p8') }) }) // 获取 reject 方式一 p8.then(null,function(err){ console.log('err === 方式一',err) }) - 通过then的catch方法
p.catch(e=>{此处是 拒绝状态的回调}).then()// 获取 reject 方式二 p8.then(function(res){ console.log('res ===',res) }).catch(e=>{ console.log('err === 方式二',e) })
catch的摆放位置也是很有讲究
解析
Promise.prototype.catch()方法是.then(null, rejection)或.then(undefined, rejection)的别名,用于指定发生错误时的回调函数。
catch在then前面
- 其实相当于这个
.then(null, rejection) - **但是如果后面再接 .then,那么其实 后面的 then 是下一个 Promise的then,而不是前面Promise的then
// demo9 catch在then的前面
let p9 = new Promise((resolve,reject)=>{
setTimeout(()=>{
// resolve('成功 == p9')
reject('失败 == p9')
})
})
p9.catch(err=>{
// catch在前面 可以 捕获 reject 和 promise内部的抛错(其实抛错最终也是 走reject的形式)
console.log('err ====',err)
}).then(function(res){
// 其实此处 已经是 catch 之后,又返回了 另外一个 promise的执行,并不是 p9,你要搞清楚
console.log('res ===',res)
},function(err){
console.log('err === then',err)
})
catch在then后面
// demo10 catch在then的后面
let p10 = new Promise((resolve,reject)=>{
setTimeout(()=>{
// resolve('成功 == p10')
reject('失败 == p10')
})
})
p10.then(function(res){
console.log('res ===',res)
}).catch(err=>{
// 注意此处 上面 then 第二个 函数必须没有,才会进入到 此处的catch 里面
console.log('err ====',err)
})
catch 捕获的错误 只能捕获同步的代码中的错误
let p9 = new Promise((resolve,reject)=>{
// throw new Error('messgae ===') // 此处的错误是能捕获到,相当于 reject 出去了
setTimeout(()=>{
throw new Error('messgae ===') // 此处的错误是异步中的,后面的 catch 和 err函数 无法捕获到
// resolve('成功 == p9')
// reject('失败 == p9')
})
})
p9.then(function(res){
console.log('res ===',res)
},function(err){
console.log('err === then',err)
})
promise中的错误捕获问题
同步代码中的报错
同步代码中的报错会被后面的catch捕获,并最终以 reject(Error)的形式结束状态
异步代码中的报错
异步代码中的报错是无法被捕获的,会影响在它生错误之后的 事件的代码执行
示例代码
let p_resolve = new Promise((resolve,reject)=>{
// throw new Error('messgae ===') // 此处的错误是能捕获到,相当于 reject 出去了
setTimeout(()=>{
// throw new Error('messgae ===') // 此处的错误是异步中的,后面的 catch 是无法捕获的
resolve('成功 == p_resolve')
// reject('失败 == p10')
})
})
let p_reject = new Promise((resolve,reject)=>{
// throw new Error('messgae ===') // 此处的错误是能捕获到,相当于 reject 出去了
setTimeout(()=>{
// throw new Error('messgae ===') // 此处的错误是异步中的,后面的 catch 是无法捕获的
// resolve('成功 == p10')
reject('失败 == p_reject')
})
})
如何将异步函数封装成Promise
这个其实很简单,我们来一个 现实中的例子就可以了,ajex的网络请求
function ajaxAsync(url) {
return new Promise(function(resolve, reject){
var client = new XMLHttpRequest(); client.open("GET", url); client.onreadystatechange = function() {
if (this.readyState !== 4) { return;
}
if (this.status === 200) {
resolve(this.response);
} else {
reject(new Error(this.statusText));
} };
client.send(); });
};
ajax('/ajax.json') .catch(function() {
console.log('失败'); })
.then(function() { console.log('成功');
})
Promise的规范解读
Promise有自己规范,只要是 符合规范的 代码对象,都可以是 Promise,你也可以自己创建自己的Promise
Promise 规范的地址
相关要求
- 对状态的要求
- Pending
- Fulfilled
- Rejected
- 必须有一个then 方法
- onFulfilled 方法的要求
- 必须是异步调用
- 必须是普通函数
- 只能被调用一次
- 不可以在 状态改变成 成功之前调用
- onReject 方法的要求
- 必须是异步调用
- 必须是普通函数
- 只能被调用一次
- 不可以在 状态改变成 失败 之前调用
- then 最后返回的也必须是一个 后续的Promise,具体规则如下 【重点&难点】
-
then接受两个参数,onFulfilled 和 onRejected,必须是函数,如果不是函数则忽略
-
onFulfilled - 如果存在,那么接受一个参数,就是 Promise 返回成功的 值
-
onRejected - 如果存在,那么接受一个参数,就是 Promise 返回失败的原因
-
onFulfilled 与 onRejected 方法必须异步调用
-
onFulfilled 与 onRejected 方法必须是一个普通函数
-
then可以被 同一个Promise 调用多次 参考demo1 【最下方有代码集合】
-
then返回一个新的 Promise,具体的处理规则比较复杂,如下[重点&难点]
- 如果then 接受的 onFulfilled 和 onRejected 不是函数(比如是一个对象,一个字符串),那么就是将 上一个 Promise的结果与状态 原封不动的 传递给 下一个 Promise 参照demo2
- 如果then接受的 onFulfilled 和 onRejected 是函数,取决于 函数调用的返回值,即的返回值(result)
- 如果 result 是一个 Promise 参照demo3
- 那么就将 此Promise 的状态和结果传递给下一个 Promise
- 如果 result 是一个 对象
- 看result 的内部,如果result对象有 then方法
- 那么 会直接调用 then 方法,并把 (onFulfilled,与onRejected)作为参数传入,最后的状态取决于 最后的函数调用。如果onfulfilled,那么就是status = Fulfilled 并将执行的结果,返回给 下一个Promise 如果调用 onRejected,那么就是 status = Rejected 并将执行的结果,返回给下一个 Promise 参照demo4
- 看result 的内部,如果result对象没有 then方法
- 那么直接将 result 作为结果返回到下一个Promise,状态为 Fulfilled 参考demo5
- 看result 的内部,如果result对象有 then方法
- 如果 result 是一个函数 同对象的处理原则一样
- 其余(不是函数与对象) demo2
- 将上一个Promise的status 和 结果 传递给下一个 Promise
- 如果then的调用函数里面报错了demo6
- status = Rejected 并将执行的结果,发送给下一个 Promise
- 如果 result 是一个 Promise 参照demo3
-
- onFulfilled 方法的要求
Promise的其他方法
Promise.resolve
相当于 new Promise(resolve,reject=>reject())
Promise.reject
相当于 new Promise(resolve=> resolve())
Promise.all
必须所有的 promise 都成功了,才算成功,只要有一个失败就失败了
Promise.race
竞赛,第一个出结果的 Promise 的状态 决定 整个的 状态,如果第一个成功,那么就是成功,第一个失败,那就是失败
Promise.finally
不管Promise 是成功还是失败,只要状态改变了,都会走这个方法,只是不知道 状态是什么
Promise.allSetted
必须所有的状态都返回了,才算结束;返回的值里面包含每一个的 状态和值
[ { status: 'fulfilled', value: 42 }, { status: 'rejected', reason: -1 } ]
如何手写一个Promise
- 我想在了解了Promise的相关规范和细节,实现个 简单的版本问题不大;
- 但是如果你是死记硬背的,那么隔几天就忘了,意义不大
- 掌握手写一个Promise的意义在于 了解Promise的规范和很多的细节
- 时间有限,后续再来完善
相关代码集合
// 主要演示,Promise的then处理的规则展示
let pro = new Promise((resolve,reject)=>{
setTimeout(()=>{
// resolve('111-Fulfilled')
reject('222 - Rejected')
})
})
// demo1 - 演示: 同一个Promise 可以多次调用
// pro.then(res=>{
// console.log('第一次调用 res ===',res)
// },err=>{
// console.log('第一次调用 err ===',err)
// })
// pro.then(res=>{
// console.log('第二次调用 res ===',res)
// },err=>{
// console.log('第二次调用 err ===',err)
// })
// demo2 - 演示: 如果then 接受的 onFulfilled 和 onRejected 不是函数(比如是一个对象,一个字符串),那么就是将 上一个 Promise的结果与状态 原封不动的 传递给 下一个 Promise
// let next_pro = pro.then('不是函数','不是函数')
// next_pro.then(res=>{
// console.log('第二次调用 res ===',res)
// },err=>{
// console.log('第二次调用 err ===',err)
// })
// demo3 - 演示: 如果then 接受的 onFulfilled 和 onRejected 是函数-函数的返回值是 promise
// let next_pro = pro.then(res=>{
// return new Promise((resolve,rejected)=>resolve('next_pro 的状态取决于我 【resolve】'))
// },err=>{
// return new Promise((resolve,rejected)=>rejected('next_pro 的状态取决于我 【rejected】'))
// })
// next_pro.then(res=>{
// console.log('next_pro的状态取决于 返回的 Promise res ===',res)
// },err=>{
// console.log('next_pro的状态取决于 返回的 Promise err ===',err)
// })
// demo4 - 演示: 如果then 接受的 onFulfilled 和 onRejected 是函数-函数的返回值是 一个对象,主要看对象有没有 then 方法
// let next_pro = pro.then(res=>{
// return {
// then:((resolve,rejected)=>resolve('next_pro 的状态取决于then方法 【resolve】'))
// }
// },err=>{
// return{
// then:((resolve,rejected)=>rejected('next_pro 的状态取决于 then方法 【rejected】'))
// }
// })
// next_pro.then(res=>{
// console.log('next_pro的状态取决于 返回的 返回对象的 then 方法 res ===',res)
// },err=>{
// console.log('next_pro的状态取决于 返回的 返回对象的 then 方法 err ===',err)
// })
// demo5 - 演示: 如果then 接受的 onFulfilled 和 onRejected 是函数-函数的返回值是 一个对象,主要看对象有没有 then 方法
// 此处演示没有then方法,就是返回一个普通对象 【相当于 resolve了过去】
// let next_pro = pro.then(res=>{
// return {
// a:1,
// nothen:((resolve,rejected)=>resolve('next_pro 的状态取决于then方法 【resolve】'))
// }
// },err=>{
// return{
// a:2,
// nothen:((resolve,rejected)=>rejected('next_pro 的状态取决于 then方法 【rejected】'))
// }
// })
// next_pro.then(res=>{
// console.log('next_pro的状态取决于 返回的 返回对象的 then 方法 res ===',res)
// },err=>{
// console.log('next_pro的状态取决于 返回的 返回对象的 then 方法 err ===',err)
// })
// demo6 - 演示: 如果then 接受的 onFulfilled 和 onRejected 是函数-函数,函数执行过程中报错了,那么相当于 reject
// let next_pro = pro.then(res=>{
// throw new Error('报错了 in res')
// },err=>{
// throw new Error('报错了 in err')
// })
// next_pro.then(res=>{
// console.log('next_pro的状态 【上一步then方法报错了,直接是 reject】 res ===',res)
// },err=>{
// console.log('next_pro的状态 【上一步then方法报错了,直接是 reject】 err ===',err)
// })
总结
- 虽然我们每天都在使用Promise,但是,恐怕很多细节都没有掌握
- 虽然只需要掌握常用的方法,就能在满足日常的开发使用,但是 当我们了解了很多的细节,那么当遇到问题的时候就不会慌,很容易排查错误