zoukankan      html  css  js  c++  java
  • Ajax请求回调地狱及解决方案(promise、async和await)

      谈及回调地狱发生得情况和解决办法,就必须追溯到原生ajax请求。

      先列出服务器提供的数据接口:

    // 服务器端接口
    app.get('/data1', (req, res) => {
      res.send('hi')
    })
    app.get('/data2', (req, res) => {
      res.send('hello')
    })
    app.get('/data3', (req, res) => {
      res.send('nihao')
    })
    
    // 启动监听
    app.listen(3000, () => {
      console.log('running...')
    })

    原生ajax请求步骤

    var xhr = new XMLHttpRequest();
    xhr.open('get','http://localhost:3000/data1');
    xhr.send(null);
    xhr.onreadystatechange = function() {
        if(xhr.readyState == 4 && xhr.status == 200) {
            // 获取后台数据
            var ret = xhr.responseText;
            console.log(ret)
        }
    }
    

      又因为发送请求的以上代码需要经过反复复用,我们就需要将它封装为函数,以减少代码的冗余度。下面请看两个封装方法(都是错误的封装):

      错误封装1:发生错误的原因是queryData函数本身没有返回值,会默认返回undefined,return ret是写在queryData的内层函数中的。

    function queryData(path) {
        var xhr = new XMLHttpRequest();
        xhr.open('get','http://localhost:3000/'+path);
        xhr.send(null);
        xhr.onreadystatechange = function() {
            if(xhr.readyState == 4 && xhr.status == 200) {
                // 获取后台数据
                let ret = xhr.responseText;
                return ret;
            }
        }
    }
    
    let res = queryData(‘data1’);
    console.log(res);  // 结果为:undefined
    

      这样很容易就让我们想到另一种封装方法——把ret在外层函数中返回。然而这就产生了另一种错误的封装效果。

      错误封装2:这种情况下发生错误的原因是ajax请求时异步的,数据ret还没有修改成功时,就已经执行了queryData函数的return代码。这时ret的值并没有被修改,当然还是null。

    function queryData(path) {
        var xhr = new XMLHttpRequest()
        xhr.open('get', 'http://localhost:3000/'+path)
        xhr.send(null)
        var ret = null
        xhr.onreadystatechange = function() {
            if (xhr.readyState == 4 && xhr.status == 200) {
                // 获取后台数据
                ret = xhr.responseText
            }
        }
        return ret
    }
    
    let res = queryData('data1')
    console.log(res)  // 结果为:undefined

      要想执行异步操作代码的返回内容,就需要使用回调函数,下面介绍一种正确的封装方法:

    function queryData(path, callback) {
      var xhr = new XMLHttpRequest();
      xhr.open('get','http://localhost:3000/' + path);
      xhr.send(null);
      xhr.onreadystatechange = function() {
        if(xhr.readyState == 4 && xhr.status == 200) {
          // 获取后台数据
          var ret = xhr.responseText;
          callback(ret);
        }
      }
    }
    
    queryData('data1',function(ret) {
      console.log(ret)    // 结果为:hi
    })
    

      但是,如果想要按顺序获取接口'data1'、'data2'、'data3'中的数据,就会进行下面的操作,也就造成了回调地狱的问题。

    queryData('data1', function(ret) {
      console.log(ret)   // 按顺序第一个输出为:hi
      queryData('data2', function(ret) {
        console.log(ret)  //按顺序第二个输出为:hello
        queryData('data3', function(ret) {
          console.log(ret)  // 按顺序第三个输出为:nihao
        });
      });
    });

    promise方式

      为了改造上面的回调地狱问题,诞生了promise。promise其实就是一种语法糖(代码形式发生改变、但是功能不变)。

    function queryData(path) {
      return new Promise(function(resolve, reject) {
        // 需要在这里处理异步任务
        var xhr = new XMLHttpRequest();
        xhr.open('get','http://localhost:3000/' + path);
        xhr.send(null);
        xhr.onreadystatechange = function() {
          // 该函数何时触发?xhr.readyState状态发生变化时
          if(xhr.readyState != 4) return;
          if(xhr.readyState == 4 && xhr.status == 200) {
            // 获取后台数据
            var ret = xhr.responseText;
            // 成功的情况
            resolve(ret);
          } else {
            // 失败的情况
            reject('服务器错误');
          }
        }
      })
    }
    
    queryData('data1')
      .then(ret=>{
        console.log(ret)    // 按顺序第一个输出为:hi
        // 这里返回的是Promise实例对象,下一个then由该对象调用
        return queryData('data2');
      })
      .then(ret=>{
        console.log(ret);  // 按顺序第二个输出为:hello
        return queryData('data3');
      })
      .then(ret=>{
        console.log(ret)  // 按顺序第三个输出为:nihao
      })
    

      对于上面代码中使用.then调用的情况,有几点说明:

    queryData('data1')
      .then(ret=>{
        console.log(ret)  // 顺序输出第一个结果为:hi
        // 如果在then方法中没有返回Promise实例对象,那么下一个then由默认产生的Promise实例对象调用
      })
      .then(ret=>{
        console.log('-------------------' + ret)  // 顺序输出第二个结果为:----------------------undefined
        // 如果在then中显式地返回一个具体数据,那么下一个then可以获取该数据
        return 456;
      })
        .then(ret=>{
        console.log('-------------------' + ret)  // 顺序输出第三个结果为:----------------------456
      })
    

      上面的代码第二个.then中return的是456,为什么能继续调用.then方法呢?

      这是因为return 456 实际上可以理解为下面的三种表示方式:

    // Promise.resolve的作用:就是把数据转化为Promise实例对象
    
    // 方式一:
    return Promise.resolve(456);
    
    // 方式二:
    return new Promise(function(resolve, reject) {
      resolve(99999);
    })
    
    // 方式三:
    Promse.resolve = function(param) {
      return new Promise(function(resolve, reject) {
        resolve(param);
      })
    }
    return Promise.resolve(88888);
    

      promise对象除了.then方法外还有两个方法可以通过 . 调用,其中.finally是ES7中新增的方法。

    .catch(ret=>{
      // 发生错误时触发
      console.log('error')
    })
    .finally(ret=>{
      // 无论结果成功还是失败都触发:一般用于释放一些资源
      console.log('finally')
    })
    

      虽然使用了promise对象,但是一路通过 . 调用方法进行下去,代码的可读性较差。

    async和await

      下面我们就提出解决回调地狱最好的一种方法,通过使用 async 和 await 

    function queryData(path) {
      return new Promise(function(resolve, reject) {
        // 需要在这里处理异步任务
        var xhr = new XMLHttpRequest();
        xhr.open('get','http://localhost:3000/' + path);
        xhr.send(null);
        xhr.onreadystatechange = function() {
          // 当readyState值不为0的时候直接返回
          if(xhr.readyState != 4) return;
          if(xhr.readyState == 4 && xhr.status == 200) {
            // 获取后台数据
            var ret = xhr.responseText;
            // 成功的情况
            resolve(ret);
          } else {
            // 失败的情况
            reject('服务器错误');
          }
        }
      })
    }
    
    async function getAllData() {
      // await执行流程是顺序执行
      let ret1 = await queryData('data1');
      let ret2 = await queryData('data2');
      let ret3 = await queryData('data3');
      console.log(ret1)
      console.log(ret2)
      console.log(ret3)
    }
    getAllData();
    

      另外,有一点需要提起注意:async函数的返回值是Promise实例对象

    async function getAllData() {
      // await执行流程是顺序执行
      let ret1 = await queryData('data1');
      return 'hello';
    }
    var ret = getAllData();
    console.log(ret)  // 这里输出一个promise对象,并且resolve的数据为hello
    ret.then(res=>{
      console.log(res)  // 这里输出结果为:hello
    })
  • 相关阅读:
    bzoj2815: [ZJOI2012]灾难
    bzoj1188: [HNOI2007]分裂游戏
    bzoj4538: [Hnoi2016]网络
    bzoj3594: [Scoi2014]方伯伯的玉米田
    bzoj2595: [Wc2008]游览计划
    bzoj3277: 串
    Django开发:(3.2)ORM:多表操作
    Django开发:(3.1)ORM:单表操作
    Django:(2)视图层&模板层
    Django开发:(1)django基础 & url控制器
  • 原文地址:https://www.cnblogs.com/belongs-to-qinghua/p/11161140.html
Copyright © 2011-2022 走看看