连续串行请求的方案对比,优雅的异步请求选择 await-to-js

287 阅读3分钟

方案概述

image.png

故事背景

用户的一次点击需求,需要连续串行触发 3 个请求,分别是数据生成、数据匹配、数据保存,每次请求的报错需要抛给用户全局提示,每次请求得到的数据是下个请求的输入,可能存在第一个请求得到的数据是最后一个的输入的情况。如何才能优雅的组织这次操作的流程环节代码?

image.png

方案的美与丑(评价体系)

评价体系(也可以用 SOLID 原则作为参考)

  1. 好拆分,代码依赖性高低(解耦性,可复用性,基石)
  2. 好改动,增加内容,或者减少内容(可维护性,基石)
  3. 清晰,代码结构明朗(阅读成本,更上一步)
  4. 数据流转,第一个请求的结果是最后一个的输入(业务需求)
  5. 报错可控:可统一,可单独处理(错误处理)
image.png

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);
}

总结脑图

这里我们就得到了第一张图

image.png