方案概述
故事背景
用户的一次点击需求,需要连续串行触发 3 个请求,分别是数据生成、数据匹配、数据保存,每次请求的报错需要抛给用户全局提示,每次请求得到的数据是下个请求的输入,可能存在第一个请求得到的数据是最后一个的输入的情况。如何才能优雅的组织这次操作的流程环节代码?
方案的美与丑(评价体系)
评价体系(也可以用 SOLID 原则作为参考)
- 好拆分,代码依赖性高低(解耦性,可复用性,基石)
- 好改动,增加内容,或者减少内容(可维护性,基石)
- 清晰,代码结构明朗(阅读成本,更上一步)
- 数据流转,第一个请求的结果是最后一个的输入(业务需求)
- 报错可控:可统一,可单独处理(错误处理)
1. 回调函数嵌套
- 解耦性:难拆分,拆分后各种传数据,依赖性高(✘)
- 可维护性:牵一发而动很多 (✘)
- 阅读成本:分不清在第几层 (✘)
- 数据流转:每一层可以得到前面几层的所有数据(✔)
- 报错可控:可统一,可单独处理(✔)
function flow(callback) {
// floor 1
axios
.get(generateUrl)
.then(r1 => {
const data = r1.data.data;
// floor 1
axios
.post(matchUrl, data)
.then(r2 => {
const data2 = r2.data.data;
// floor 3
axios
.post(saveUrl, data2)
.then(r3 => {
const data3 = r3.data.data;
// floor 4
callback(data3); // result to user view
})
.catch(axiosError);
})
.catch(axiosError);
})
.catch(axiosError);
}
2. 链式 Promise
- 解耦性:每一层一个 promise,但大函数太长了(✘)
- 可维护性: (✔)
- 阅读成本:比回调地狱好点,但大函数 then 太多了 (✔)
- 数据流转:最后一层拿第一层数据需要一层层传,或者设置变量(✘)
- 报错可控:只能统一处理(✘)
function flow() {
let data1 = undefined;// 存储第一个 promise 结果
return (
axios
.get(generateUrl)
// chain 1
.then(r1 => {
const data1 = r1.data.data;
return axios.post(matchUrl, data1);
// chain 2
})
.then(r2 => {
const data2 = r2.data.data;
return axios.post(saveUrl, data2);
// chain 3
})
.then(r3 => {
const data3 = r3.data.data;
return [data1,data3];
})
// common error handler
.catch(axiosError)
);
}
3. async & await 的 try catch
- 解耦性:结构清晰(✔)
- 可维护性:拆分合理,原子化 (✔)
- 阅读成本:一眼明了 (✔)
- 数据流转:不用层层传递(✔)
- 报错处理: try catch 统一处理(✘)
async function generate() {
return axios.get(generateUrl);
}
async function match(payload) {
return axios.post(matchUrl, payload);
}
async function save(payload) {
return axios.post(saveUrl, payload);
}
async function flow() {
try {
// 如果 try 的内容特别多,也不好看到 catch
const r1 = await generate();
const r2 = await match(r1.data.data);
const r3 = await save(r2.data.data);
} catch (e) {
axiosError(e);
}
}
// or 回调地狱
async function flow() {
try {
const r1 = await generate();
try {
const r2 = await match(r1.data.data);
try {
const r3 = await save(r2.data.data);
return [r1, r3];
} catch (e) {
axiosError(e);
}
} catch (e) {
axiosError(e);
}
} catch (e) {
axiosError(e);
}
}
4. await-to-js
像写文章一样写代码
- 解耦性:结构清晰(✔)
- 可维护性:拆分合理,原子化 (✔)
- 阅读成本:一眼明了 (✔)
- 数据流转:不用层层传递(✔)
- 报错处理:或分或合,或返回(✔)
import { to } from "await-to-js";
// 底层原子函数
async function generate() {
return axios.get(generateUrl);
}
async function match(payload) {
return axios.post(matchUrl, payload);
}
async function save(payload) {
return axios.post(saveUrl, payload);
}
// 流程处理中心(数据)
async function flow() {
const [err1, r1] = await to(generate());
if (err1) return [err1]; // return axiosError(err1)
const [err2, r2] = await to(match(r1.data.data));
if (err2) return [err2];
const [err3, r3] = await to(save(r2.data.data));
if (err3) return [err3];
return [null, r3];
}
// 用户触发函数,(状态管理),loading 之类
async function onClick() {
const [err, result] = await flow();
if (err) axiosError(err);
else setResult(result);
}
总结脑图
这里我们就得到了第一张图