回调地狱以及解决回调地狱

3,152 阅读6分钟

什么是回调地狱

如果在回调函数中再传入一个函数,就会出现一个嵌套结构如此层层嵌套回调就会形成回调地狱。

业务中有时在请求数据时会出现这种情况,需要发送多个AJAX请求,第1个正常发送,但是第2个需要第1个请求的数据结果中的某一个值作为参数,第3个请求需要第2个请求的数据结果中的某一个值作为参数,如此层层嵌套回调就是回调地狱。这样的代码就很难去维护且业务逻辑复杂代码可读性也不好。

//有多个异步任务,要求需要同时拿到所有异步任务的结果,下边就是用回调地狱
$.get("url", (res1) => {
    conosle.log(res1)
    $.get("url+res1", (res2) => {
        conosle.log(res2)
        $.get("url+res2", (res3) => {
            conosle.log(res3)
            $.get("url+res3", (res4) => {
                conosle.log(res4)
            })
        })
    })
})

Promise解决回调函数

Promise是一个对象里面保存着某个未来才会结束的事件就是一个异步操作的结果,从它可以获取异步操作的消息。Promise对象的出现就是进行处理各种异步操作,将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数也就可以用于解决回调地狱。

$getJSON("/post/1.json").then(function(post) {
  return getJSON(post.commentURL);
}).then(function (comments) {
  console.log("resolved: ", comments);
}, function (err){
  console.log("rejected: ", err);
});

第一个then方法传入的回调函数,返回的是另一个Promise对象。这时,第二个then方法传入的回调函数,就会等待这个新的Promise对象状态发生变化,只有新的Promise对象状态改变了才会触发后面代码的执行。如果变为resolved,就调用第一个回调函数,如果状态变为rejected,就调用第二个回调函数。这样就将异步操作以同步操作的流程表达出来了

Promise封装AJAX

以下这个函数用AJAX技术请求时就会返回一个Promise对象。

function promise_ajaxmine(url) {
    return new Promise((resolve, reject) => {
        try {
            //AJAX技术
        let xhr = new XMLHttpRequest() || ActiveXObject("MirosoftXMLHTTP");
        xhr.open("GET", url, true);
        xhr.send();
        xhr.onreadystatechange = function (){
            if (xhr.readyState == 4){
                if(xhr.status == 200){
                    //网络请求成功返回正确的业务数据
                    resolve(xhr.responseText);
                }else if(xhr.status == 404){
                    //网络请求成功返回错误的业务数据
                    reject(xhr.responseText)
                }else{
                    //网络请求失败
                    reject("page not found");
                };
            }
        };
        } catch (error) {
            //当try的大括号中throw抛出错误时以及出现了错误js语法才会执行catch,此时就把异步操作失败产生的数据作为返回值。
            resolve(xhr.responseText);
        };
    });
};

多次网络请求需要嵌套时避免层层回调使用方法

let p1=promise_ajaxmine('/ajax1')
    p1.then((data)=>{
        console.log(111111111,data)
            return promise_ajaxmine('/ajax2')
    })
    .then((data2)=>{
        console.log(data2)
            return promise_ajaxmine('/ajax3')
    })
    .then((data3)=>{
        console.log(data3)
            return promise_ajaxmine('/ajax4')
    })
    .then((data4)=>{
	console.log(data4)
    })
    .catch((e)=>{
	console.log(e)
    })

Axios和Fetch解决回调地狱

Axios网络请求工具

Axios是一个基于Promise对象的网络请求框架,它只是一个网络请求工具并不是网络请求技术,前端网络请求技术有AJAX技术和JSONP技术以及Fetch技术,这个网络请求框架可以用于浏览器和node.js。使用时需要先引入框架到项目中才能使用。这个框架网络请求依然不能解决跨域问题,需要代理服务器才能跨域请求。

多次网络请求需要嵌套时避免层层回调使用方法

let p1=axios('/ajax1')
    p1.then((data)=>{
        console.log(111111111,data)
            return axios('/ajax2')
    })
    .then((data2)=>{
        console.log(data2.data)
            return axios('/ajax3')
    })
    .then((data3)=>{
        console.log(data3.data)
            return axios('/ajax4')
    })
    .then((data4)=>{
	console.log(data4.data)
    })
    .catch((e)=>{
	console.log(e)
    })

Fetch

Fetch是XMLHttpRequest的升级版也是单独的一个前端网络请求技术。Fetch原理是使用Promise语法不使用回调函数,用于在JavaScript脚本里面发出HTTP请求,是浏览器自带的API,后端node.js不能使用。在用法上,fetch()接受一个 URL 字符串作为参数,默认向该网址发出 GET 请求,返回一个 Promise 对象。fetch()接收到的数据需要先进行数据转码,来得到想要的数据。

多次网络请求需要嵌套时避免层层回调使用方法

fetch("/ajax3")
.then((response)=>{
    return response.json()//得到 JSON 对象
})
.then((data)=>{
    console.log(data)//真正请求到的数据				
})
   读取内容的方法:
   response.text():得到文本字符串。
   response.json():得到 JSON 对象。
   response.blob():得到二进制 Blob 对象。
   response.formData():得到 FormData 表单对象。
   response.arrayBuffer():得到二进制 ArrayBuffer 对象。
  上面5个读取方法都是异步的,返回的都是 Promise 对象。必须等到异步操作结束,才能得到服务器返回的完整数据。

async/await

async/await其实是Promise的语法糖,它能实现的效果都能用then函数链式调用来实现,它是为优化then函数链式调用而开发出来的。

1.async用于修饰函数,它可以修饰function声明的函数也可以修饰对象的方法以及箭头函数,async表示函数是异步的。async定义的函数内部会默认返回一个Promise对象,如果函数内部抛出异常或者是函数返回reject或者函数内部存在js语法错误,都会使函数的Promise对象状态为失败rejected。async函数接收到返回的值,发现不是异常或者reject,则判定Promise对象状态为成功fulfilled,这里可以return所有js数据类型,字符串,布尔值,数字类型等都是判定Promise对象状态为成功fulfilled。

async function fn(){
                return("234");
            }
            console.log(fn(),111);
            async function fm(){
                return Promise.reject('执行失败')
            }
            fm().then((data)=>{
                console.log(data,2222);
            })
            .catch((err)=>{
                console.log(err,2222);
            });

image.png

2.await就表示等待某个操作完成,语法上强制规定await只能出现在asnyc修饰的函数中。await操作符后面可以是任意值。但当await后面是Promise对象的时候,会暂停async修饰的异步函数执行。也就是说,必须得等待await后面的Promise对象处理完成才能继续函数中后面代码的运行,它会阻塞函数中后面的代码但是不会阻碍这个异步函数后面的js代码,等着Promise对象状态改变了,然后得到异步操作返回的结果,就作为await表达式的运算结果。如果它等到的不是一个 Promise对象,那await表达式的运算结果也会将等到的值转换成一个 的Promise对象。则async函数必须等到内部所有的 await 命令的 Promise对象执行完,async函数的返回值Promise对象才会发生状态改变。此时才能执行then方法或者catch方法得到数据。

async function fn(){
                let data=await setTimeout(()=>{
                    return "delay";
                },1000);
                console .log(123);
                return data;
            };
            console.log(fn(),111);
            console.log(456);

image.png

3.async修饰的异步函数里如果有多个await的时候,如果其中任意一个抛出异常或者报错了,都会导致函数停止执行,async函数的返回值Promise对象直接rejected,函数中就可以采用try-catch语法。

async function fn(){
                let data=await Promise.reject(404);
                console.log("not go on")
                return 234;
            };

image.png

函数中就可以采用try-catch语法

async function fn(){
                try {
                let data=await Promise.reject(404);
                console.log("not go on");
                } catch (error) {
                    console.log(error);
                } finally{
                    return "无论怎样都会执行";
                }
            };
            fn().then((data)=>{
                console.log(data,111);
            })
            .catch((err)=>{
                console.log(err,222)
            });

image.png

多次网络请求需要嵌套时避免层层回调使用方法

async function fn(){
    let data1=await new Promise((resolve,reject)=>{resolve(2000)})
    console.log(data1)				
    let data2=await axios('/ajax2')
    console.log(data2)				
    let data3=await axios('/ajax3')
    console.log(data3)
    let data4=await axios('/ajax4')
    console.log(data4)
}