Promise解决回调深渊(callback hell)
promise是ES6提供的异步编程的一种解决方案,比传统的解决方案——回调函数和事件更加优雅。在讲promise之前有必要补充点ajax异步操作的知识。
Ajax的callback hell (回调深渊)
Ajax (异步 JavaScript 和 XML),是指一种创建交互式、快速动态网页应用的网页开发技术,无需重新加载整个网页的情况下,能够更新部分网页的技术。通过在后台与服务器进行少量数据交换,Ajax 可以使网页实现异步更新。这意味着可以在不重新加载整个网页的情况下,对网页的某部分进行更新。下面将一个ajax封装成函数:
function ajax(url,successCallback,failCallback){
//1、创建XMLHttpRequest对象
var xmlhttp
if(window.XMLHttpRequest){
xmlhttp=new XMLHttpRequest()
}else{
xmlhttp=new ActiveXObject('Microsoft.XMLHTTP')
}
//2、发送请求
xmlhttp.open('GET',url,true)
xmlhttp.send()
//3、服务端的响应
xmlhttp.onreadystatechange=function(){
if(xmlhttp.readyState===4 && xmlhttp.status===200){
let obj=JSON.parse(xmlhttp.responseText)
successCallback && successCallback(obj)//判断成功回调函数是否传递 传递则执行 没传递就不执行
}else if(xmlhttp.readyState===4&&xmlhttp.status===404){
failCallback && failCallback(xmlhttp.statusText)//同理判断是否传递了失败函数
}
}
}
当我们要用这样封装的ajax去回调另一个ajax再回调另一个ajax呢?就会变成这样:
(假设在文件夹static下有a.json、b.json、c.json)
a.json
{
"a": "我是A"
}
b.json
{
"b": "我是B"
}
c.json
{
"c": "我是C"
}
ajax('static/a.json', res => {
console.log(res)
ajax('static/b.json', res => {
console.log(res)
ajax('static/c.json', res => {
console.log(res)
})
})
})w
这种层层嵌套的代码格式,显然显得冗余又容易出错,这就被称为 “回调地狱” 或者“回调深渊”。而Promise风格就解决了这种层层嵌套的代码风格,使得代码扁平化。
Promise
Promise,可以理解为一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果,Promise对象有以下两个特点:
- 对象的状态不受外界影响。Promise对象有三种状态:pending(进行中)、Fullfilled(已成功)和Rejected(已失败)。只有异步操作的结果可以决定当前是哪一种状态,仍和其他操作都无法改变这个状态
- 一旦状态改变就不会再变,任何时候都可以得到这个结果。Promise对象的状态改变只有两种可能:Pending—>Fulfilled、Pending—>Rejected。只要这两种情况有一种发生,状态就凝固了不会再变了,而是一直保持这个结果,这时就称为Resolved(已定型)。为了行文方便,后续的Resolved统一指的是Fulfilled状态。
基本语法
Promise.prototype.then()
let promise = new Promise((resolve,reject) => {
// ...some code
if(/*异步操作成功*/){
resolve(res)
}else{
reject(err)
}
}).then((res) => {/*success*/}, (err) => {/*error*/})
Promise构造函数接受一个函数作为参数,该函数的两个参数分别是resolve和reject两个函数,由js引擎提供,不用自己部署。
resolve将Promise的状态从Pending—>Resolved,reject将Promise的状态从Pending—>Rejected,当状态变成Resolved时,会执行then的第一个函数参数,并将resolve函数的参数传进去(res),当状态变成Rejected时,会执行then的第二个函数参数,并将reject的函数的参数传进去(err)。可以结合下面这张图来理解Promise状态的变化。
下面来看一个小例子:
let p=new Promise((resolve,reject)=>{
setTimeout(()=>{
console.log('hello')
resolve('成功')//promise状态不可逆
reject('失败')
},1000)
}).then((res)=>{
console.log(res)
},(err)=>{
console.log(err)
})
//成功
Promise.prototype.catch()
除了用以上Promise对象的then方法的第二个函数参数捕获异常,也可以使用 Promise 对象的 catch 方法来捕获异步操作过程中出现的任何异常。:
function test() {
return new Promise((resolve, reject) => {
reject(new Error('es'))
})
}
test().catch((e) => {
console.log(e.message) // es
})
看到上面这个例子,可能会有疑问,catch捕获的到底是reject还是Error,到底是谁触发了这个捕获?
function test() {
return new Promise((resolve, reject) => {
throw new Error('wrong')
})
}
test().catch((e) => {
console.log(e.message) // wrong
})
对比以上两个例子可以感受出来,Error和reject都可以触发catch,而在第一个例子中,也有Error对象,但它不是throw,而是reject的一个对象,catch捕获的reject,在第二个例子中,catch则捕获的是throw error。
注意
不建议在 Promise 内部使用 throw 来触发异常,而是使用 reject(new Error()) 的方式来做,因为 throw 的方式并没有改变 Pronise 的状态
Promise解决callback hell
现在再回头看,Promise是怎么解决回调深渊的问题,先用最原始的Promise来理解一下代码扁平化:
new Promise((resolve,reject)=>{
ajax('static/a.json',res=>{
console.log(res)
resolve()
})
}).then(()=>{
console.log('a成功')
return new Promise((resolve,reject)=>{
ajax('static/b.json',res=>{
console.log(res)
resolve()
})
})
}).then(()=>{
console.log('b成功')
return new Promise((resolve,reject)=>{
ajax('static/c.json',res=>{
console.log(res)
resolve()
})
})
}).then(()=>{
console.log('c成功')
可以看出,上面的代码还可以再简洁一点,现在将Promise封装起来试试:
function getPromise(url) {
return new Promise((resolve, reject) => {
ajax(url, res => {
resolve(res)
}, err => {
reject(err)
})
})
}
getPromise('static/a.json')
.then(res => {
console.log(res)
return getPromise('static/b.json')
}).then(res => {
console.log(res)
return getPromise('static/c.json')
}).then(res => {
console.log(res)
}).catch(err => {
console.log(err)
})
这里要注意的时,当使用Promise的then方法是,单独对一个异常的捕获,不影响后续then方法的执行,当使用的是catch方法时,会统一捕获异常,使得后续不再执行,可以对比下面两段代码:
getPromise('static/aa.json')//aa.json不存在
.then(res=>{
console.log(res)
return getPromise('static/b.json')
},err=>{
console.log(err)
return getPromise('static/b.json')
}).then(res=>{
console.log(res)
return getPromise('static/c.json')
}).then(res=>{
console.log(res)
})
//Not Found
//{b: "我是b"}
//{c: "我是c"}
getPromise('static/a.json')
.then(res=>{
console.log(res)
return getPromise('static/bb.json')
}).then(res=>{
console.log(res)
return getPromise('static/c.json')
}).then(res=>{
console.log(res)
}).catch(err=>{
console.log(err)
})
//{a: "我是a"}
//Not Found