资讯专栏INFORMATION COLUMN

异步请求回调嵌套解决方案

Winer / 2012人阅读

摘要:前言在前端异步请求中,要获取数据,传统的写法是回调,但这种方法不利于代码的维护和可读性,所以时代通过和解决了这种问题,更是通过和使其更加简洁通过替代回调嵌套的特点是初始化后,调用一次方法会暂停在关键字前,同时也可以在方法里传值,使对应的语句

前言

在前端异步请求中,要获取数据,传统的写法是ajax回调,但这种方法
不利于代码的维护和可读性,所以es6时代通过Generator和Promise解决了这种问题,es7更是通过async和await使其更加简洁

通过Generator替代回调嵌套
Generator的特点是初始化后,调用一次next方法会暂停在yield关键字前,同时也可以在next方法里传值,使对应的yield语句获取到

首先编写准备代码

const axios = require("axios")

const http = axios.create({
  baseURL: "http://127.0.0.1:89",
  timeout: 3000,
  headers: {"Accept": "application/json"}
});


let it;

定义的it是迭代器的意思,现在还没有值

异步请求调用方法

function call(url,options={}) {
    setTimeout(()=>{
        if ( !it ) {
            throw new Error("请初始化生成器")
        }
        http(url,options)
        .then(v=>{
            
            it.next(v.data)  // 生成器传递参数,并且启动下一次执行
        })
    },0)
}

setTimeout包裹,是确保这段代码是异步执行,如果不加,同步执行的it判断可能会抛出异常

编写生成器,这里是主要的异步请求逻辑处理

/**
    多个请求互相依赖
    1. 根据用户名,密码获取用户凭证
    2. 根据用户凭证获取用户数据id列表
    3. 获取数据列表中第一条数据详情
*/
function * getData() {
    const data1 = yield call("/login", { method: "POST", data: JSON.stringify({user:"root",pass:"123456"}) })
    console.log("我是结果1:",data1)
    const data2 = yield call("/list", { method: "POST", headers: { token: data1.data.token } })
    console.log("我是结果2:",data2)
    const data3 = yield call("/item", { method: "POST", headers: { token: data1.data.token }, data: JSON.stringify({ id:data2.data.ids[0] }) })
    console.log("我是结果3:",data3)  
}

调用,运行

it = getData()
it.next()

这里给it附上值,然后会触发第2步的代码

运行流程

首先我们定义了生成器,运行它的时候,只需执行it.netx,然后会运行第一个yield后面的语句,并停在第一个yield语句处,当call函数里的异步请求执行完毕,会将异步请求的结果it.next(data)传递给第一个yield前面的取值语句,然后会执行到第二个yield语句后面的call,以此类推,直到整个生成器执行完毕

Generator + Promise

单个通过Generator已经可以解决大部分异步嵌套的问题,但是不够完善,要确保it初始化,必须让整个call异步执行,代码不够优雅,而且依赖外部it,结构分散,所以我们用Generator + Promise可以进一步完善

简化call方法

function call(url,options={}) {
  return http(url,options)
}

去掉在call里执行it.next

增加外部调用生成器next函数run

function run (g) {
  const it = g();  // 初始化生成器, 注意这里的冒号
  
  (function each(res) {
      // 根据生成器的返回结果进行判断
      if (!res.done && res.value instanceof Promise ) {  // 如果是Promise返回
          res.value.then(v=>{
              each( it.next(v.data) )    // 这里是方案一的call里的next并传值到下一次next 
          })
      } else if (res.done) {    // 生成器执行结束, 运行结束
          return
      } else {
          throw new Error("yield 后面请用返回Promise的函数")
      }
  })(it.next())    //自运行
}

运行

run(getData)

运行方法一中的getData生成器,得到的结果一样

getData里yield后的函数扩展

根据run函数可知,只要it.next返回的结果是Promise即可正常运行,那么在getData里如下写法也是可以的

function * getData() {
  const data1 = yield http("/login", { method: "POST", data: JSON.stringify({user:"root",pass:"123456"}) })
  console.log("我是结果1:",data1)
  const data2 = yield http("/list", { method: "POST", headers: { token: data1.data.token } })
  console.log("我是结果2:",data2)
  const data3 = yield http("/item", { method: "POST", headers: { token: data1.data.token }, data: JSON.stringify({ id:data2.data.ids[0] }) })
  console.log("我是结果3:",data3)  
}

**这里的http函数是axios的一个实例,返回值为Promise,
外层加call是可以在call里写一些异常,或者测试处理,类似Reactdva处理方式**

ES7处理方式

如果你觉得方案二还是有些繁琐,那么可以试试ES7的await语法

改造getData函数如下

/**
    多个请求互相依赖
    1. 根据用户名,密码获取用户凭证
    2. 根据用户凭证获取用户数据id列表
    3. 获取数据列表中第一条数据详情
*/
async function getData() {
    const { data:data1 } = await call("/login", { method: "POST", data: JSON.stringify({user:"root",pass:"123456"}) })
    console.log("我是结果1:",data1)
    const { data:data2 } = await call("/list", { method: "POST", headers: { token: data1.data.token } })
    console.log("我是结果2:",data2)
    const { data:data3 } = await call("/item", { method: "POST", headers: { token: data1.data.token }, data: JSON.stringify({ id:data2.data.ids[0] }) })
    console.log("我是结果3:",data3)  
}

和方案二比较这个方法头部多了async关键字,去掉了*号,yield换成了await,这是ES7异步函数的声明方式。
注意返回值不是通过方案二中next res.data注入,所以获取到的是整个res,取值的时候注意拿结果里的.data数据

运行

getData()

结果和方案一二一样,这种方式更加简洁易懂

方案三简单完整代码

个人比较喜欢简洁有效的代码,所以推荐方案三

const axios = require("axios")

const http = axios.create({
  baseURL: "http://127.0.0.1:89",
  timeout: 3000,
  headers: {"Accept": "application/json"}
});


/**
    多个请求互相依赖
    1. 根据用户名,密码获取用户凭证
    2. 根据用户凭证获取用户数据id列表
    3. 获取数据列表中第一条数据详情
*/
async function getData() {
    const { data:data1 } = await http("/login", { method: "POST", data: JSON.stringify({user:"root",pass:"123456"}) })
    console.log("我是结果1:",data1)
    const { data:data2 } = await http("/list", { method: "POST", headers: { token: data1.data.token } })
    console.log("我是结果2:",data2)
    const { data:data3 } = await http("/item", { method: "POST", headers: { token: data1.data.token }, data: JSON.stringify({ id:data2.data.ids[0] }) })
    console.log("我是结果3:",data3)  
}

getData() //运行

以上代码仅完成了核心功能,一些防御性和异常处理不完善,仅供理解和学习

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/106585.html

相关文章

  • javascript异步中的回调

    摘要:如果你把函数的指针地址作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。 同期异步系列文章推荐谈一谈javascript异步javascript异步与promisejavascript异步之Promise.all()、Promise.ra...

    WalkerXu 评论0 收藏0
  • Promise到底解决了什么问题?

    摘要:我的博客大家都知道解决了回调地狱的问题。这就是异步的嵌套带来的可读性的问题,它是由异步的运行机制引起的。在与第三方团队沟通之后问题得到了解决。这不但使代码变得臃肿不堪,还进一步加剧了可读性的问题。的特征保证了可以解决信任问题。 我的github博客 https://github.com/zhuanyongxigua/blog 大家都知道Promise解决了回调地狱的问题。说到回调地狱,...

    yibinnn 评论0 收藏0
  • 从源码看 Promise 概念与实现

    摘要:从源码看概念与实现是异步编程中的重要概念,它较好地解决了异步任务中回调嵌套的问题。这些概念中有趣的地方在于,标识状态的变量如都是形容词,用于传入数据的接口如与都是动词,而用于传入回调函数的接口如及则在语义上用于修饰动词的副词。 从源码看 Promise 概念与实现 Promise 是 JS 异步编程中的重要概念,它较好地解决了异步任务中回调嵌套的问题。在没有引入新的语言机制的前提下,这...

    kel 评论0 收藏0
  • 【笔记】 你不知道的JS读书笔记——异步

    摘要:异步请求线程在在连接后是通过浏览器新开一个线程请求将检测到状态变更时,如果设置有回调函数,异步线程就产生状态变更事件,将这个回调再放入事件循环队列中。 基础:浏览器 -- 多进程,每个tab页独立一个浏览器渲染进程(浏览器内核) 每个浏览器渲染进程是多线程的,主要包括:GUI渲染线程 JS引擎线程 也称为JS内核,负责处理Javascript脚本程序。(例如V8引擎) JS引擎线程负...

    junnplus 评论0 收藏0
  • Promise学习总结

    摘要:引擎线程也称为内核,负责处理脚本程序例如引擎引擎线程负责解析脚本,运行代码。对象代表一个未完成但预计将来会完成的操作。注意一旦新建就会立即执行它属于,无法取消。 写在前面: 第一遍学Promise时, 只是大概过了一遍, 感觉学的不够深入, 这一篇算是对之前的一个总结吧. Promise在ES6中也属于一个较难理解的一部分; 所以在学习一个比较难理解的知识点时, 我们可以围绕这个知识点...

    twohappy 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<