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 写法容易。