JavaScript:Promise链与async/await的区别?

15

我正在学习Javascript中的Promiseasync/await。下面的示例代码以异步方式在node.js(我的node.js版本是v10.0.0)中读取和解析JSON文件。

在示例代码中,ChainReadJson函数和AwaitReadJson函数做着相同的事情,都是读取和解析JSON文件。不同之处在于ChainReadJson函数使用了Promise链,而AwaitReadJson函数使用了async/await。

const FS = require("fs");

function ReadFile(fileName) {
    return new Promise((Resolve, Reject) => {
        FS.readFile(fileName, 'utf8', (error, result) => {
            if (error)
                Reject(error);
            else
                Resolve(result);
        });
    });
}

// function using promise chain

function ChainReadJson(fileName, CallBack) {
    ReadFile(fileName)
        .then(
            res => JSON.parse(res),
            err => {
                Message(-1, err.message);
            }
        )
        .then(
            res => {
                if (res !== undefined)
                    CallBack(fileName, res);
            },
            err => {
                Message(-2, err.message);
            }
        );
}

// function using async/await

async function AwaitReadJson(fileName, CallBack) {
    let res, json;

    try {
        res = await ReadFile(fileName);
    }
    catch (err) {
        Message(-1, err.message);
        return;
    }
    try {
        json = JSON.parse(res);
    }
    catch (err) {
        Message(-2, err.message);
        return;
    }
    CallBack(fileName, json);
}

ChainReadJson('test.json', PrintJSON);
AwaitReadJson('test.json', PrintJSON);

// common functions

function PrintJSON(fileName, json) {
    console.log(`JSON[${fileName}]:`, json);
}

function Message(n, str) {
    console.log(`[${n}]`, str);
}

使用Promise链编写ChainReadJson函数时,我在控制执行结果和错误方面遇到了困难。但是,当使用async/await编写AwaitReadJson函数的代码时,这些困难大部分都消失了。

我是否正确理解了async/await的优点?与Promise链相比,async/await有哪些缺点?

(示例代码是此答案中的代码的修改版本。原始代码仅使用Promise链,并编写以确切知道链中发生错误的位置和错误信息)


1
你很好地理解了 Promise 链和 async/await,其他的都是观点问题,这个网站不擅长提供。就我个人而言,忽略这一点,除了它不被所有地方支持之外,async/await 实际上没有任何缺点。 - generalhenry
你使用 Promise 链时遇到了哪些错误?你是如何使用 ChainedPromiseJSON 的?这两个代码不同,在 Promise 链中,只有在 JSON.parse 的返回值不是 undefined 时才执行回调函数,而在 await 的情况下,总是执行回调函数。 - cowbert
@generalhenry:谢谢您的意见。 - pdh0710
@cowbert:示例代码正常工作。如果您阅读此答案,您可以更轻松地理解示例代码。 - pdh0710
你为什么创建了两个单独的问题。无论如何,如果前一步(第一个“then”)出现错误,则将在第二个“then”中触发错误回调。检查res !== undefined只有在JSON.parse返回undefined时才会执行,但这永远不会发生,因为它会抛出SyntaxError。 - cowbert
@cowbert:之前的问题不是关于async/await的,而且这个示例代码在我的node.js(v10.0.0)中可以正常工作。看起来你仍然没有理解这个答案 - pdh0710
2个回答

13

事实上,async/await设计用来减少样板代码,使得相比回调函数、Promise和生成器函数,编写异步程序更加容易。

  • 虽然Promises的目标相同,但它们还有额外的限制,必须在现有的JS引擎中工作,因此其语法更加复杂。使用async/await需要一个相对较新的JS引擎。如果你正在编写自己的node.js应用程序,则可能无关紧要,但是库可能需要与旧版本的node.js兼容(我不确定是否可以将其转换为支持生成器的旧浏览器)。
  • 由于async/await是较新的,它没有得到很好的优化。去年进行的比较报告表明,Bluebird promises(一种实现简化版promise的JS库)在某些基准测试中优于async/await。(当然,在您的用例仅涉及少量网络请求时,这可能无关紧要)。
  • 如果您需要并行执行多个异步操作(如果需要它们的结果), 您仍然可能需要使用promises。

谢谢您的回答并纠正我的问题。这对我来说是一个很大的帮助。同时,我同意您的观点,即这是异步/等待的一个缺点:“使用异步/等待需要相对较新的JS引擎”。 - pdh0710
关于这个问题:"仍然需要使用Promise来并行执行多个异步操作" ... 当我同时执行多个async/await函数时,它们会自然地并行工作。我认为这是一个自然的结果,因为async/await函数基于Promise操作。我的理解正确吗? - pdh0710
如果您所说的“执行”是指在没有使用await的情况下调用异步函数,那么是的。但由于通常关心异步操作的结果(即使只是出于错误处理的考虑),您必须处理返回的Promise。 - Nickolay
我使用了await执行了几个异步函数。顺便问一下,没有await的异步函数有用吗? - pdh0710
如果你的意思是 await f1();await f2();,那么不,它们并 是并行运行的,查看 MDN 上的这个示例,它还演示了为什么会在不使用 await 的情况下调用异步函数。我现在得走了,如果你有进一步的问题,请尝试发布另一个问题或寻找实时聊天--stackoverflow 评论不是一个良好的沟通工具。 - Nickolay

4

虽然 async/await 可以是清理异步逻辑的好方法,但值得指出的是,promise 逻辑可以被显着地简化,甚至非常类似于 async/await 的替代方案:

const fs = require("fs");
const util = require("util")

//Could also use the experimental "fs/promise" api from node v10
const promisifiedReadFile = util.promisify(fs.readFile);

const readFile = (fileName) => promisifiedReadFile(fileName, 'utf8');

function chainReadJson(fileName, callback) {
    return readFile(fileName)
        .then(json => JSON.parse(json))
        .then(result => callback(null, result))
        .catch(e => {
            console.log("Error reading or parsing file", e.message);
            callback(e)
        });
}

这里唯一的功能区别是所有错误日志记录都在链的末尾发生。保留readFile和JSON.parse的分割日志是可能的,但这有点棘手。通常情况下,您希望在处理错误后重新抛出它们,以便跳过下游的.then处理程序:但是,如果您再次抛出错误,它将被下游的.catch处理程序捕获,如果您找不到一种方法来过滤它,这将导致重复记录。这是可行的,但有点麻烦,所以我在上面的代码中省略了它。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接