[removed] promise chain vs. async/await?

2020-07-06 00:21发布

问题:

I am learning about Javascript Promise and async/await. The sample code below asynchronously reads and parses a JSON file in node.js (my node.js version is v10.0.0).

In the sample code, ChainReadJson function and AwaitReadJson function are doing the same thing, reading and parsing a JSON file. The difference is that ChainReadJson function uses a promise chain, while AwaitReadJson function uses 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);
}

When writing the code for ChainReadJson function using promise chain, I had difficulties controlling execution results and errors. However, when writing the code for AwaitReadJson function using async/await, those difficulties are mostly disappeared.

Do I correctly understand the benefits of async/await? What are the disadvantages of async/await compared to promise chain?

(The sample code is a modified version of the code in this answer. The original code uses promise chain only, and is written to know exactly where in the chain the error occurred and what is the error)

回答1:

Indeed, async/await were designed to reduce boilerplate and make asynchronous programs easier to write, compared to callbacks, promises, and generator functions.

  • While promises were created with the same goal, they had the additional constraint of having to work in the existing JS engines -- so their syntax is more complicated. Using async/await requires a relatively new JS engine. It might not matter if you're writing a node.js app of your own, but a library might need to be compatible with older node.js versions (and I'm not sure if you can transpile it for use in older browsers without generator support).
  • Since async/await is newer, it's not as optimized. A comparison made in the last year reports Bluebird promises (a JS library implementing simplified version of promises) outperforming async/await in a certain benchmark. (Of course this may not matter when your use-case is making a few network requests.)
  • You might still need promises to execute several asynchronous actions in parallel (edit: if you need their results)


回答2:

While async/await can be a nice way to cleanup asynchronous logic, it's worth pointing out that the promise logic can be cleaned up significantly, to the point of being very similar to the async/await alternative:

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)
        });
}

The only functional difference here is that all the error logging occurs at one place, at the end of the chain.

It's possible to preserve the split logging for the readFile and the JSON.parse, but that is admittedly a bit trickier. You generally want to re-throw errors after handling them, so that the downstream .then handlers are skipped: but if you throw the error again, it'll be caught again by the downstream .catch handlers, which will cause duplicate logging, if you don't find a way to filter it out.

It's doable, but it's a bit of a pain, so I left it out of the above code.