如果是单个ajax请求,不带有接口钱数据相互依赖的,其实怎么请求都没关系.
jQuery
,axios
,vue-resource
,XMLHttpRequest
但一点有了接口间数据的相互依赖,那么问题就来了.
由于,每一个请求都是独立的,且回调的时机不确定,为了保证请求接口数据返回的过程可控和相互依赖关系,可能需要费点周折.
1. ajax callback
$.get('url',function(data){
$.get('url2' + data.id,function(data2){
$.get('url3' + data2.id,func(data){
// 拿到最终数据
})
})
})
使用最原始的方案,ajax callback 这种 回调函数嵌套的方式,在请求数据上面是没有问题的.在一个接口请求完毕之后,拿到数据,在接着请求下一个接口,很好理解,也很好书写.
问题在于:
当接口依赖多了之后,整个代码的嵌套层级就会变多,代码应该的竖向发展,变成了横向.
这样可能会导致代码结构不清晰,不便于后期的维护.
2. Promise
function pget(url) {
return new Promise(function(reslove,reject){
$.get(url,function(data){
if (data.err) reject(data.err)
resolve(data)
})
})
}
pget('url')
.then(res=>{
return pget('url2' + res.id)
})
.then(res=>{
return pget('url3' + res.id)
})
.then(res=>{
// 拿到最终数据
})
使用 then
确实能把callback
那种横向的趋势变成更加符合代码风格的纵向.
问题在于:
then 太多了,且语义化不强,前面的
then
也许还知道是干什么的,到了后面可能就懵逼了.
3. Generator
function *gen() {
let data1 = yield $.ajax('url')
let data2 = yield $.ajax('url2' + data1.id)
yield $.ajax('url3') // 注意,在jQuery v3.0+版本,$.ajax() 支持了 promise
}
let g = gen()
g.next().value
.then(res=>{
g.next(res.id).value
.then(res=>{
g.next(res.id).value
.then(res=>{
// 拿到数据了,该干嘛干嘛.
})
})
})
问题在于
- 相比
$.ajax()
函数嵌套,变的更加复杂。 - 相比
promise
属性变的更多。(.next().value)
三种方式.
- 第一种 ajax callback 存在回调函数,结构是横向发展的.
- promise 确实结构变清晰了,在每一次的
reslove
返回一个新的promise
,把这种接口依赖的操作用then
纵向连接了起来. - generator 就有点扯淡了,不光需要
next()
还要.value
,更加扯淡的是,嵌套层级和$.ajax()
没有区别,甚至于比$.ajax()
写的代码更多了.
我们所希望的
不管是 $.ajax() callback
还是 promise then
或者是 generator .next()
.
我们都希望都以同步的方式去写异步代码.
let data = 异步请求数据(url)
let data2 = 异步请求数据(url2 + data.id)
let data3 = 异步请求数据(url3 + data2.id)
console.log(data,data1,data2)
由于 generator
可以一次一次的拿到 promise
,并且可以暂停执行.
可以根据 generator
的这个特性来实现以同步代码的写法来执行异步任务
function readFilePromise(path) {
return new Promise(function (reslove, reject) {
fs.readFile(path, 'utf8', (err, data) => {
if (err) reject(err)
reslove(data)
})
})
}
function* gen2() {
let data1 = yield readFilePromise('./1.txt')
console.log(`data1:${data1}`)
let data2 = yield readFilePromise('./2.txt')
console.log(`data2:${data2}`)
}
此段代码,正常情况下,每一次 next()
拿到了的对象 .value
都是一个 promise
.
核心思想是,我们等待 promse
对象,执行完毕了,在继续执行下一个 yield
.
function genRunner(gen) {
let g = gen() // 拿到迭代器指针
function next(data) {
let nextObj = g.next(data)
if (!nextObj.done) { // 如果迭代没有完成
// 等待当前这个 promise执行完成了.
nextObj.value.then(res=>{
// 继续下一次的迭代.并把结果传递给当前的next()方法.
next(res)
})
}
}
next() // 第一此调用第一个 yield ,第一个yield没办法接受参数.
}
测试一下:
genRunner(gen2)
结果:
data1:1111111111
data2:22222222222
所以,我们可以利用 generator
& runner
的工具,在 generator
中像写同步代码那样书写异步代码.
genRunner
函数到底干了些什么事情?
- 首先,
generator
我们是可以手动调用next()
一步步执行的. -
genRunner
首选拿到 迭代器指针,指向第一个next()
返回的obj
- 根据这个
obj.done
判断迭代器是否遍历完毕.-
如果没有遍历完毕,
- 就先拿到这个
obj.value
,也就是promise
对象,执行它的then
. - 等待这个
promise
的then
执行完毕之后,再次手动调用next()
- 就先拿到这个
如果执行完毕了,就什么也不做.
-
常用的工具还有 co
npm i co
co(gen2)
data1:1111111111
data2:2222222222
问题在于
如果我们非要使用
generator
发送异步请求的话,那么为了不写恶心的嵌套代码,就需要借助第三方co
,runner
这样的插件.(或者自己写一个)
使用 ES7 推出的 async / await
function readFilePromise(path) {
return new Promise(function (reslove, reject) {
fs.readFile(path, 'utf8', (err, data) => {
if (err) reject(err)
reslove(data)
})
})
}
async function readFile() {
let res1 = await readFilePromise('./1.txt')
let res2 = await readFilePromise('./2.txt')
}
结果:
data1:1111111111
data2:2222222222
async & await 基本是个语法糖,它结合了 generator
& 类似 runner
| co
的功能,能让我们很舒服的用同步代码的方式写异步代码.