·
Across the Great Wall, we can reach every corner in the world.

js 的异步编程可以分为 4 种写法,如下图:

无论后端的异步代码还是前端的异步请求,目前公认最好的写法应该是:“写起来像同步,运行起来像异步”,这样才能发挥两者的优点:

  • 异步:效率高,时间轴紧凑
  • 同步:易开发,易调试

Promise 写法最大的问题就是复杂请求时:then.then.then 无限写下去,中间难以调试,也能难插入自动化测试工具,await 写法优雅不少,写起来像同步,运行起来像异步,配合 all/race 写法可以满足大部分需求。

但是 js 请求中数据竟态问题用 await 写法实现就很麻烦了,考虑这样一个竟态场景:

点击用户 A 的主页,发出请求 request_a,但是请求还没完成的时候切换到用户 B 的主页,发起请求 request_b,但由于网络问题,request_b 比 request_a 先到(顺序反了),结果 request_b 的数据被后到的 request_a 给覆盖了,导致用户 B 的主页显示用户 A 的数据。

这样的 bug 很场景,无论你用的 vuex 还是 redux 做数据管理都可能出现,竟态问题用 await 写法搞定很麻烦,需要写不少代码做检测,为了应对复杂异步编程的需求,js 其实有更好的写法(也是我们目前广泛使用的): yield + generator ,一个经典案例就是 redux-saga,写起来非常优雅,和 await 写法类似,但是能够实现代码分割、解耦,调试非常方便(无需一行代码,使用中间件即可),写起来像这样:

import { call, put, takeEvery, takeLatest } from 'redux-saga/effects'

function* fetchData() {
    const res1 = yield call(axios.get, 'abc');
    const res2 = yield call(axios.get, 'def')}

function* rootSaga() {
    yield takeLatest("FETCH_DATA", fetchData);
}

一行代码,加入 takeLatest 就可以解决竟态问题(只接收最后发出请求的结果),这样即使 request_a 数据后到,也会被抛弃,因为 request_a 先发出了请求。

最后一种写法就是 observable 流式写法,典型的就是 redux-observable,控制功能更强大,但是入门门槛也高,写法像异步编程,调试还是没有 generator 写法容易。

Replies
1

heart 写的好详细!多谢了