用 ES6 解决你的回调地狱

本文探讨如何使用 ES6 的 Promise、Generator 和 Async/Await 解决回调地狱问题,通过实例展示了它们在处理异步操作中的应用,以及各自的优势和限制。重点介绍了如何优化错误处理和值传递,提高代码可读性和维护性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

用 ES6 解决你的回调地狱

最近系统的学了一下 ES6,发现以往编码的项目还有很多可以改进的空间,所以我使用 ES6 对部分过去的项目进行重构

Promise

这是之前用 node.js 写的一个接口,查询作者信息和当前用户的关系,基本思路是

  1. 查询粉丝和博主的关系(关注,未关注)
  2. 查询博主的所有粉丝

里面有一个 connection 查询是很明显的回调地狱,让我们用 Promise 解决掉它

app.get("/authorInfo", function (req, res) {
  const fanId = req.query.fan_id;
  const authorId = req.query.blogger_id;
  const r = obEmpty();
  connection.query(
    "select * from fans where blogger_id = ? and fan_id = ?;",
    [authorId, fanId],
    function (error, result) {
      if (error) throw error;
      // 结果基类一定要初始化置空
      r.code = 200;
      r.msg = "success";
      r.data = {};
      r.data.relate = result.length ? true : false;
      connection.query(
        "select count(*) as total from fans where blogger_id = ?;",
        [authorId],
        function (error, result) {
          if (error) throw error;
          r.data.fanTotal = result[0].total;
          res.send(r);
        }
      );
    }
  );
});

使用 Promise 解决后

app.get("/authorInfo", function (req, res) {
  const fanId = req.query.fan_id;
  const authorId = req.query.blogger_id;
  const promise = new Promise((resolve, reject) => {
    connection.query(
      "select * from fans where blogger_id = ? and fan_id = ?;",
      [authorId, fanId],
      function (error, result) {
        if (error) reject(error);
        resolve(result.length ? true : false);
      }
    );
  });

  promise
    .then((value) => {
      connection.query(
        "select count(*) as total from fans where blogger_id = ?;",
        [authorId],
        function (error, result) {
          if (error) throw error;
          res.send(
            new Result(200, "success", {
              relate: value,
              fanTotal: result[0].total,
            })
          );
        }
      );
    })
    .catch((err) => {
      console.error("服务器出错了" + err);
      res.send(new Result(200, "查询失败"));
    });
});

代码行数由 28 => 37,但优化了错误处理,值传递的清晰性和代码可读性,这里其实并不能很明显的看出 Promise 解决回调地狱的明显优势,而且这上面还存在一些问题,

问题一

如果在 promise.then() 里面中出现了错误我们应该如何处理?

问题二

一开始的 Promise 没有需要返回的信息但和 promise.then() 中的函数存在关系,此时 resolve 要怎么处理?同理 then 之中要怎么做?

对于第一个问题,可以使用 vs code 简单的看一下 promise 中的声明文件(.d.ts)

/**
 * Attaches callbacks for the resolution and/or rejection of the Promise.
 * @param onfulfilled The callback to execute when the Promise is resolved.
 * @param onrejected The callback to execute when the Promise is rejected.
 * @returns A Promise for the completion of which ever callback is executed.
 */
then<TResult1 = T, TResult2 = never>(onfulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | undefined | null, onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | undefined | null): Promise<TResult1 | TResult2>;

里面说明了,then(() => {…}) 中返回值是作为 TResult1,所以我们使用 return 可以为下一个 then(() => {…}) 传递信息,这点与一开始的 promise 不同,因为其中的 resovle() 函数返回值为 void

所以第一个的解决方案如下

const promise = new Promise((resolve, reject) => {
  resolve(true);
});

promise
  .then(() => {
    // 抛出一个 Promise 对象,并设置状态为 reject
    return new Promise((resolve, reject) => {
      reject("出错了");
    });
  })
  .then(() => {
    console.log("in");
  })
  .catch((err) => {
    throw err;
  });

// 我觉得下面这种更方便

const promise = new Promise((resolve, reject) => {
  resolve(true);
});

promise
  .then(() => {
    throw new Error("出错了");
  })
  .then(() => {
    console.log("in");
  })
  .catch((err) => {
    throw err;
  });

对于第二个问题

是否可以 return 一些值表示状态呢?可以使用 vs code 简单的看一下 promise 中的声明文件(.d.ts)

/**
 * Creates a new Promise.
 * @param executor A callback used to initialize the promise. This callback is passed two arguments:
 * a resolve callback used to resolve the promise with a value or the result of another promise,
 * and a reject callback used to reject the promise with a provided reason or error.
 */
new <T>(executor: (resolve: (value: T | PromiseLike<T>) => void, reject: (reason?: any) => void) => void): Promise<T>;

executor() 返回值为 void,返回值没有意义,使用 resolve 才能够传递相应的状态,所以即使没有需要返回的数据也应该表示一下

resolve();

,如果你什么都不干,new Promise() 返回的状态为 pending,这种状态是无法进入到下一个 then() 的,Promise 的几种状态可以去百度一下

而在 then(() => {}) 你什么都不返回,其实是没有问题的,默认返回 undefined

用 3 个或以上的例子就能够更好的看清楚,后来我又找到了一个例子,工作流程为

  1. 读取服务器文章 HTML 文件中的内容
  2. 将更新的 HTML 的内容写入服务器文章 HTML 文件中
  3. 最后更新数据库中文章的标题
app.get("/updateArticle", function (req, res) {
  // 对象模型解构属性名称需相同
  let { title, articleName, aid, html } = req.query;
  // 可以对 articleName 作一定校验
  articleName = config.remoteFileDefaultPath + articleName;
  fs.readFile(articleName, (err, data) => {
    if (err) console.error(err);
    const root = HTMLParser.parse(data.toString());
    root.querySelector(".main").innerHTML = html;
    fs.writeFile(articleName, root.innerHTML, (err) => {
      if (err) console.error(err);
      const sql = "UPDATE new SET new_name = ? WHERE new_id = ?;";
      connection.query(sql, [title, aid], function (error, results) {
        if (error) throw error;
      });
    });
  });
});

变更如下

app.get("/updateArticle", function (req, res) {
  // 对象模型解构属性名称需相同
  let { title, articleName, aid, html } = req.query;
  // 可以对 articleName 作一定校验
  articleName = config.localFileDefaultPath + articleName;

  const promise = new Promise((resolve, reject) => {
    fs.readFile(articleName, (err, data) => {
      if (err) reject(err);
      const root = HTMLParser.parse(data.toString());
      root.querySelector(".main").innerHTML = html;
      resolve(root.innerHTML);
    });
  });

  promise
    .then((htmlContent) => {
      fs.writeFile(articleName, htmlContent, (err) => {
        if (err) throw err;
      });
    })
    .then(() => {
      const sql = "UPDATE new SET new_name = ? WHERE new_id = ?;";
      connection.query(sql, [title, aid], function (error, results) {
        if (error) throw error;
        res.send(new Result(200, "更新成功"));
      });
    })
    .catch((err) => {
      console.error("服务器出错了" + err);
      res.send(new Result(200, "查询失败"));
    });
});

代码结构清晰了很多,可能看到这里已经有人想说,这代码也 TM 太烂了,用 SpringBoot 根本不会写得这么烂,的确用 SpringBoot 几个层来写肯定不会这么烂,主要原因是这是使用 express 写的,而且当初也只是随便写写,根本没考虑这么多,而且现在使用 node.js 的更完善的框架已经能够避免这些问题了

生成器(Generator)

写了这么多不如把标题改成 用 Promise 解决你的回调地狱,其实不然,还可以使用生成器(Generator)来解决回调

用生成器解决后

app.get("/updateArticle", function (req, res) {
  // 对象模型解构属性名称需相同
  let { title, articleName, aid, html } = req.query;
  // 可以对 articleName 作一定校验
  articleName = config.localFileDefaultPath + articleName;

  function readFile() {
    fs.readFile(articleName, (err, data) => {
      if (err) throw err;
      const root = HTMLParser.parse(data.toString());
      root.querySelector(".main").innerHTML = html;

      iterator.next(root.innerHTML);
    });
  }
  function writeFile(htmlContent) {
    fs.writeFile(articleName, htmlContent, (err) => {
      if (err) throw err;
      iterator.next();
    });
  }
  function update() {
    const sql = "UPDATE new SET new_name = ? WHERE new_id = ?;";
    connection.query(sql, [title, aid], function (error, results) {
      if (error) throw error;
      res.send(new Result(200, "更新成功"));
      iterator.next();
    });
  }

  function* gen() {
    const htmlContent = yield readFile();
    yield writeFile(htmlContent);
    yield update();
  }

  const iterator = gen();
  try {
    iterator.next();
  } catch (error) {
    throw error;
  }
});

优势就是解决了回调地狱,但劣势和 Promise 比较还是比较突出的,我认为是过于耦合,参数传递需要通过 iterator,为了解决回调地狱牺牲过大

Async await

Async await 算是基于 Promise 的另一种方式,我以前大部分都是配合 axios 使用,但这里并不需要 Promise,重构后如下

app.get("/updateArticle2", async function (req, res) {
  // 对象模型解构属性名称需相同
  let { title, articleName, aid, html } = req.query;
  // 可以对 articleName 作一定校验
  articleName = config.localFileDefaultPath + articleName;

  try {
    const htmlContent = await (() => {
      return new Promise((resolve,reject) => {
        fs.readFile(articleName, (err, data) => {
          if (err) throw err;
          const root = HTMLParser.parse(data.toString());
          root.querySelector(".main").innerHTML = html;
          resolve(root.innerHTML);
        });
      })
    })();

    await fs.writeFile(articleName, htmlContent, (err) => {
      if (err) throw err;
    });
    
    const sql = "UPDATE new SET new_name = ? WHERE new_id = ?;";
    
    await connection.query(sql, [title, aid], function (error, results) {
      if (error) throw error;
      res.send(new Result(200, "更新成功"));
    });
  } catch (err) {
    console.error("服务器出错了" + err);
    res.send(new Result(200, "查询失败"));
  }
});

async await 重构后代码结构更清晰,相对于 Promise 链式调用要更灵活一些,但有些时候可能又没有直接 Promise 链式调用好,比如上面的三个回调都要返回数据,则三个回调都要在外层套上 Promise,如果是简单的代码其实得不偿失,没有必要,总而言之就是没有最好的代码,只有最合适的代码。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值