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

前言

在前端异步请求中,要获取数据,传统的写法是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.next,然后会运行第一个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() //运行

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

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值