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
}
这可能需要根据具体用法进行一些更改,但通常回调具有error 和result 作为两个参数。这将转换为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 语句。两者都实现了相同的嵌套方式。