前端几种异步的处理方式

如果是单个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)

三种方式.

  1. 第一种 ajax callback 存在回调函数,结构是横向发展的.
  2. promise 确实结构变清晰了,在每一次的 reslove 返回一个新的 promise ,把这种接口依赖的操作用 then 纵向连接了起来.
  3. 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.
      • 等待这个 promisethen 执行完毕之后,再次手动调用 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 的功能,能让我们很舒服的用同步代码的方式写异步代码.

你可能感兴趣的:(前端几种异步的处理方式)