前言
在前端异步请求中,要获取数据,传统的写法是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里写一些异常,或者测试处理,类似React
的dva
处理方式**
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() //运行
以上代码仅完成了核心功能,一些防御性和异常处理不完善,仅供理解和学习