欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

前端异步编程方案演变

程序员文章站 2022-05-17 16:45:19
1. 异步的演变 首先假设要渲染一个页面,只能异步的串行请求a,b,c,然后才能拿到页面的数据并请求页面 针对于不同的异步编程方式,我们会得到如下的代码: 1.1 回调...

1. 异步的演变

首先假设要渲染一个页面,只能异步的串行请求a,b,c,然后才能拿到页面的数据并请求页面

针对于不同的异步编程方式,我们会得到如下的代码:

1.1 回调函数

// 假设request是一个异步函数
request(a, function () {
    request(b, function () {
        request(c, function () {
            // 渲染页面
        })
    })
})

回调函数的嵌套是愈发深入的。在不断的回调中,request(a)回调函数中的其他逻辑会影响到request(b),request(c)中的逻辑,同理,request(b)中的其他逻辑也会影响到request(c)。在这个例子中,request(a)调用request(b),request(b)调用request(c),request(c)执行完毕返回,request(b)执行完毕返回,request(a)执行完毕返回。我们很快会对先后顺序产生混乱,从而很难直观的分析出异步回调的结果。这就被称为回调地狱。

为了解决这种情况,es6新增了promise对象。

1.2 promise

// 假设request是一个promise函数
request(a).then(function () {
    return request(b)
}).then(function () {
    return request(c)
}).then(function () {
    // 渲染页面
})

promise对象用then函数来指定回调。所以,之前在1.1中回调函数的例子可以改为上文中的模样。可以看到,promise并没有消除回调地狱,但是却通过then链将代码逻辑变得更加清晰了。在这个例子中,request(a)调用request(b),request(b)调用request(c),request(c)执行完毕返回。现在,request(a)中的内容只能通过显示声明的data来影响到request(c)——如果没有显示的在回调中声明,则影响不了request(c),换言之,每段回调被近乎独立的分割了。

但是promise本身还是有一堆的then,还是不能让我们像写同步代码一样写异步的代码,因此js又引入了generator。

1.3 generator

function* gen(){
    var r1 = yield request(a)
    var r2 = yield request(b)
    var r3 = yield request(c)
    // 渲染页面
};

generator是协程在es6上的实现,协程是指一个线程上不同函数间执行权可以相互切换。如本例,先执行gen(),然后在遇到yield时暂停,执行权交给request(a),等到调用了next()方法,再将执行权还给gen()。

通过协程,js就实现了用同步的方式写异步的代码,但是generator的使用要配合执行器,这自然是麻烦的。于是就有了async/await。

generator的自动执行器是co函数库,有兴趣的同学可以通过《co 函数库的含义和用法》来进行了解。

1.4 async/await

async function gen() {
    var r1 = await request(a)
    var r2 = await request(b)
    var r3 = await request(c)
    // 渲染页面
}

如果比较代码的话,1.4的代码只是把1.3的代码中* => async,yield变为await。但async函数的实现,就是将 generator函数和自动执行器,包装在一个函数里[1]。spawn就是自动执行器。

async function fn(args){
  // ...
}

// 等同于

function fn(args){ 
  return spawn(function*() {
    // ...
  }); 
}

除此以外,async函数比generator函数有更好的延展性——yield接的是promise函数/thunk函数,但await还可以包括普通函数。对于普通函数,await表达式的运算结果就是它等到的东西。否则若await等到的是一个promise函数,await就会协程到这个promise函数上,直到它resolve或者reject,然后再协程回主函数上[2]。当然,async函数也比generator函数更加易读和易理解。

2. 总结

本文阐述了从回调函数到async/await的演变历史。async函数作为换一个终极解决方案,尽管在并行异步处理上还要借助promise.all(),但其他方面已经足够完美。

参考文档

《深入掌握 ecmascript 6 异步编程》系列 《理解javascript的 async/await》