【问题标题】:How to refactor the following snippet by using async/await如何使用 async/await 重构以下代码段
【发布时间】:2020-10-11 10:39:31
【问题描述】:
import { get } from "request";
import { writeFile } from "fs";

get(
  "https://en.wikipedia.org/wiki/Async/await",
  (requestErr, response, body) => {
    if (requestErr) {
      console.error(requestErr);
    } else {
      writeFile("async.js", body, writeErr => {
        if (writeErr) {
          console.error(writeErr);
        } else {
          console.log("File written");
        }
      });
    }
  }
);

据我了解,writeFile 函数需要异步启动,因为它需要先等待 get 函数完成(获取 URL)。但是,我真的不确定如何重构它。

【问题讨论】:

  • 已经完成(调用writeFile时解决)

标签: javascript asynchronous import async-await


【解决方案1】:

寻找这样的东西?

var axios = require("axios");
var fs = require("fs");

const fetchAndWrite = async () => {
  try {
    const { data } = await axios.get(
      "https://en.wikipedia.org/wiki/Async/await"
    );
    await fs.promises.writeFile("/some_directory/async-await.html", data);
  } catch (error) {
    // something bad happened...
  }
};

fetchAndWrite();

【讨论】:

  • 你也可以用来自fs.promises的promise版本替换fs.writeFileSync()
  • 对@Sirko :) 我已经在答案中添加了它。请注意,它仍然是一个实验性 API。
  • 作者,看 VLAZ 的回答,更完整:)
【解决方案2】:
import { get } from "request";
import { writeFile } from "fs/promises";
import { promisify } from "util";

const getAsync = promisify(get);

async function main() {
  let body;
  try {
    ({ body } = await getAsync("https://en.wikipedia.org/wiki/Async/await"));
  } catch(requestErr) {
    console.error(requestErr);
    return;
  }
  
  try {
    await writeFile("async.js", body);

    console.log("File written");
  } catch (writeErr) {
    console.error(writeErr);
  }
}

main();

将任何回调流转换为 Promise 和 async/await 的通用指南

检查库是否有 promise API

如果是,请使用它。许多库都有一个双重接口,您可以通过回调调用它们,也可以不调用它们,它们会返回一个承诺。或者他们可能有替代导入来获得承诺版本。

如果没有,您需要convert the callbacks to promises。请记住,这可以通过现有的库来完成。

最后一种选择是使用不同的库,但这有点极端。您必须检查它的工作方式是否与您使用当前的方式相同。不过,如果有更新且维护得更好的库,它有时是一个不错的选择。

将回调转换为async/await

一般转换如下:

来自

callbackAPI(data, function(error, result) {
    if (error) {
        //handle error
    } else {
        //process result
    }
});

//assume the promisifiedAPI is the promise returning alternative of callbackAPI

try {
    const result = await promisifiedAPI(data);
    //process result
} catch (error) {
    //handle error
}

这可能需要根据具体用法进行一些更改,但通常回调具有errorresult 作为两个参数。这将转换为try..catch 构造,其中现在承诺的调用是awaited,然后返回然后处理result,如果没有问题,则在try 中处理。错误处理在 catch 块中。

另外,如果还没有top level await,则需要将整个异步代码包装成async function() {}async () => {},否则无法使用await


此处采取的步骤

检查库是否有 promise API

request

该库没有内置的 Promise API。 The documentation suggests to use libraries that convert the calls to promises。一个简单的方法是使用utils.promisify() 来实现这一点,因为它已经在 Node.JS 中了。

在检查这一点时,我还发现了 How do you properly promisify request? (answer by Little Roys),它正是关于这个库的。那里的代码(将其更改为仅关注get):

import { get, post } from "request";
import { promisify } from "util";

const [getAsync, postAsync] = [get, post].map(promisify);

或者如果我们只想关注get()

import { get } from "request";
import { promisify } from "util";

const getAsync = promisify(get);

另外值得注意的是,request 包已被弃用。在这种情况下,更改为不同的库是一个可行的替代方案。不需要,但有可能。我将在整个示例中使用这个来展示正常的转换是如何发生的。

fs

它是does have a promise API,你可以从"fs/promises" 导入东西。 Here is the promise version of writeFile.

在这种情况下,改变就足够了

import { writeFile } from "fs";

import { writeFile } from "fs/promises";

将回调转换为async/await

require

get(
  "https://en.wikipedia.org/wiki/Async/await",
  (requestErr, response, body) => {
    if (requestErr) {
      console.error(requestErr);
    } else {
      //omitted for brevitiy
    }
  }
let body;
try {
  ({ body } = await getAsync("https://en.wikipedia.org/wiki/Async/await"));
} catch(requestErr) {
  console.error(requestErr);
  return;
}

这将调用现在承诺的getAsync 并从响应中提取body

如果有错误,我们会在catch 块中处理它。如果出现错误,return; 会立即终止函数。我稍后会回到这个。

现在我们有了响应的body,我们可以继续

fs

writeFile("async.js", body, writeErr => {
  if (writeErr) {
    console.error(writeErr);
  } else {
    console.log("File written");
  }
});
try {
  await writeFile("async.js", body);

  console.log("File written");
} catch (writeErr) {
  console.error(writeErr);
}

我们使用await 调用writeFile,确保只有在操作完成后才会恢复执行。我们没有得到它的响应,我们只是监视try..catch 的错误并在出现时处理它们。如果writeFile 成功,我们只需转到console.log("File written");

async function main()

async function main() {
  //execute promisified operations
}

main();

整个块被包装到一个异步函数中,所以可以使用await。然后我们调用该函数。这种方式更具可读性。如前所述,您可能支持顶级await。这可能是在未来,也可能是通过转译。或者您的代码可能已经在异步函数中。在这些情况下,您不需要另一个异步函数,但我想展示一下以防万一。

try {} catch(error) { return; }

最后,我想谈谈这个结构。我不喜欢块的嵌套。如果以这种方式重写代码,则可以避免它(为简洁起见,代码 sn-p 折叠):

import { get } from "request";
import { writeFile } from "fs/promises";
import { promisify } from "util";

const getAsync = promisify(get);

async function main() {
  
  try {
    const { body } = await getAsync("https://en.wikipedia.org/wiki/Async/await");
    
    try {
      await writeFile("async.js", body);

      console.log("File written");
    } catch (writeErr) {
      console.error(writeErr);
    }
  } catch(requestErr) {
    console.error(requestErr);
  }
}

main();

但是,如果您需要多个异步调用来进一步嵌套块,这就会成为一个问题。我更喜欢通过return 提前退出,这样可以将块保持在一个级别。如果您更喜欢(并且能够)在调用链的上游处理故障,另一种方法是使用throw 语句。两者都实现了相同的嵌套方式。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-11-29
    • 1970-01-01
    • 1970-01-01
    • 2021-10-02
    • 2019-01-10
    • 1970-01-01
    相关资源
    最近更新 更多