ES6|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
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值