用 ES6 解决你的回调地狱

简介: 最近系统的学了一下 ES6,发现以往编码的项目还有很多可以改进的空间,所以我使用 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,如果是简单的代码其实得不偿失,没有必要,总而言之就是没有最好的代码,只有最合适的代码。

相关文章
|
1月前
|
JavaScript 前端开发
js开发:请解释什么是ES6的async/await,以及它如何解决回调地狱问题。
ES6的async/await是基于Promise的异步编程工具,简化了代码并提高可读性。它避免回调地狱,将异步操作转化为Promise,使得代码同步化。错误处理更直观,无需嵌套回调或.then()。
24 1
|
1月前
|
前端开发 JavaScript 程序员
|
11天前
|
前端开发 JavaScript 开发者
JavaScript中的异步操作与回调地狱解决方法
JavaScript中的异步操作与回调地狱解决方法 在现代的Web开发中,JavaScript扮演着极为重要的角色,尤其是在处理网络请求、文件操作或者任何可能耗费时间的操作时。为了不阻塞程序的执行,JavaScript 提供了异步编程模型。本文将介绍JavaScript中的异步操作是什么,什么是回调地狱,以及如何解决回调地狱问题。 什么是异步操作? 异步操作指的是那些不会立即完成的操作,程序可以在等待异步操作完成的同时,继续执行其他代码。JavaScript通常使用事件循环机制处理异步操作,这使得它可以在不阻塞主线程的情况下执行任务。 常见的异步操作包括: 网络请求(如使用 XMLHt
9 2
|
1月前
|
前端开发 JavaScript
在JavaScript中,如何处理回调地狱问题
为解决回调地狱问题,可以采取三种策略:1) 使用命名函数,将回调函数提取为独立函数,减少嵌套;2) 采用Promise的链式调用,清晰组织异步操作并统一错误处理;3) 使用ES8的async/await,编写近似同步的异步代码,提高可读性和错误处理效率。这些方法能提升代码的可读性和可维护性。
|
1月前
|
前端开发 JavaScript
js开发:请解释Promise是什么,以及它如何解决回调地狱(callback hell)问题。
Promise是JavaScript解决异步操作回调地狱的工具,代表未来可能完成的值。传统的回调函数嵌套导致代码难以维护,而Promise通过链式调用`.then()`和`.catch()`使异步流程清晰扁平。每个异步操作封装为Promise,成功时`.then()`传递结果,出错时`.catch()`捕获异常。ES6的`async/await`进一步简化Promise的使用,使异步代码更接近同步风格。
38 1
|
1月前
|
前端开发 JavaScript
回调地狱(Callback Hell)
回调地狱(Callback Hell),也称为回调金字塔或异步嵌套噩梦,是JavaScript以及其它支持回调编程范式的语言中常见的一种现象。**`在处理多个连续的异步操作时`**,如果每个操作都依赖于前一个操作的结果并使用嵌套回调函数来实现,那么随着异步层级的增长,代码会变得极其深陷且难以理解和维护。
|
1月前
|
前端开发 JavaScript
用原生JavaScript(ES5)来实现Promise的等效功能(异步回调)
用原生JavaScript(ES5)来实现Promise的等效功能(异步回调)
|
1月前
|
JavaScript 前端开发
|
前端开发 小程序 Java
小程序不同页面的异步回调,callback和promise的使用讲解
小程序不同页面的异步回调,callback和promise的使用讲解
174 0
|
前端开发
前端学习案例9-promise和回调地狱1
前端学习案例9-promise和回调地狱1
49 0
前端学习案例9-promise和回调地狱1