我可以在 NodeJS 中混合使用回调和 async/await 模式吗?

Can I mix callbacks and async/await patterns in NodeJS?

我在互联网上查找了答案,但我找不到我要找的东西。

我想知道下面的代码有什么问题(前提是我们应该避免混淆回调和承诺)-

function a(callback){
    (async ()=>{
        try{
           let result = await doSomething();
           callback(null, result);
        } catch(e){
           log('error at await');
           callback(e, null);
        }
    })()
}

如果我在代码中使用上述模式可以吗?

Can I mix callbacks and async/await patterns in NodeJS?

可以。不推荐。

混合回调和承诺时,通常更难进行正确的错误处理。此外,控制流可能真的是一团糟。这个例子并不太乱,因为只有一个异步操作,但为什么不只是 return 承诺并加入异步设计的现代时代并完全跳过使用任何普通回调?此外,这里的等待是毫无意义的。您只是让事情变得比 return doSomething() 复杂得多,并且让调用者使用 returned 承诺。

因此,您可以将 9 行函数体(包括 async IIFE)替换为 1 行简单代码。

不混合普通回调和承诺的原因

  1. 首先,所有首先使用 promises 而不是普通回调的原因。我不会重复所有这些,因为它们已经被分析和重复了很多次。引入任何简单的回调只会否定很多首先使用 promises 的原因。
  2. 100% 基于 Promise 的代码通常会比两者的任何混合更紧凑和更简单(一旦您了解了 Promise 以及如何最好地编写代码)。
  3. 当您混合使用 promise 和普通回调时,控制流会变得非常复杂。每个人都有一个如何做的模型。一旦你有多个并行或顺序异步操作,普通回调就会复杂得多(这就是为什么当我们只有普通回调时必须存在诸如 async 库之类的库的原因)而这种级别的流控制是自然的并且内置承诺。然后,尝试混合控制流和错误处理的两种模型,事情很快就会变得复杂起来。
  4. 许多开发人员在尝试混合使用两者时在错误处理方面犯了错误,未能正确传播错误。出现这些错误的原因是,当你混音的时候,做正确的事情会更复杂。
  5. Promises 是 Javascript/nodejs 中异步编程的现在和未来。我们甚至很快就会拥有顶级 await,甚至可能甚至是基于承诺的异步导入。为什么要使用不再被视为语言的现在或未来的旧模型来增加复杂性。
  6. 如果您有一些不 return 承诺的异步操作,那么您可以更恰当地使用它们,方法是在它们周围放置一个承诺包装器(可能使用 util.promisify()),然后仅在他们对您的实际实施和控制流程的承诺形式。

人们告诉您不要将两者混合使用是在低估您别无选择的情况。一心一意是软件工程的悲剧

是的,promises (async/await) 设计模式就是未来。但是,当一个库使用回调并且您的设计实现了承诺 (async/await) 设计模式时,您别无选择,只能混合使用这两种技术。例如,当您为异步操作设计数据库包装器库时,您使用的是使用回调的库,例如 neDB。

如果你有一个使用回调的库,但你已经进入 21 世纪并且正在使用 async/await,例如,那么你可以执行这样的操作:

注意:我在“promise -> then -> catch”场景中放置了“reject”和“catch”,以简单地强调您可以使用任何一种方法。

/**
 * Test function that expects a callback. Set's a future execution of the callback.
 * @param {Number} a 1st test value.
 * @param {Number} b 2nd test value.
 * @param {Number} c 3rd test value.
 * @returns {Number} Simply "((a * b) + c)".
 */
function test1(a, b, c, cb) {
    return setTimeout(() => {
        return cb((a * b) + c);
    }, 1000);
}

/**
 * Test function that expects a callback. Makes an immediate execution of the callback.
 * @param {Number} a 1st test value.
 * @param {Number} b 2nd test value.
 * @param {Number} c 3rd test value.
 * @returns {Number} Simply "((a * b) + c)".
 */
function test2(a, b, c, cb) {
    return cb((a * b) + c);
}

/**
 * Execute a promise returning method that invokes a method that executes a callback upon completion. Returning the the callback's
 * result as the promise resolve.
 * @param {Function} method Actual method to invoke.
 * @param {Function} callback Callback to execute to retrieve the result.
 * @returns {Any} The result of the callback invocation.
 */
async function executeAsyncCallback(method, callback) {
    return new Promise((resolve, reject) => {
        try {
            /** Handler method for returning the promise (async) result from the callback invocation. */
            const handler = function () {
                try {
                    let s = "callback(";
                    let first = false;
                    for (let key in arguments) {
                        if (first) s += ','; else first = true;
                        s += `arguments['${key}']`;
                    }
                    s += ");";
                    return resolve(eval(s));
                } catch (err) {
                    reject(err);
                }
            };

            /** 
             * First stage is to execute the method that invokes the callback, which is our handler above which invokes the callback 
             * with the parameters supplied by the method.
             */
            let s = "method(";
            let first = false;
            for (let key in arguments) {
                let arg = arguments[key];
                if (arg === method || arg === callback)
                    continue;
                if (first) s += ','; else first = true;
                s += `arguments['${key}']`;
            }
            s += (first ? ',' : '') + `handler);`;
            return eval(s);
        } catch (err) {
            reject(err);
        }
    });
}

/**
 * Test callback. If the input == 5 then return the test object, otherwise simulate an exception.
 * @param {Object} input Test result.
 * @returns {Object} Sample {@link Object} .
 */
const testcb = (input) => {
    console.log(`Test callback: Result = ${input} ...`);
    let out = { beep: input, boop: input * 2 };
    if (input === 5)
        return out;
    throw new Error(JSON.stringify(out));
}

/**
 * Test a normal promise -> then -> catch scenario.
 */
console.log('Returned promise: ', executeAsyncCallback(test1, testcb, 1, 2, 3).then(function () {
    for (let key in arguments) {
        console.log(`Promise -> then: '${key}': '${JSON.stringify(arguments[key])}'`);
    }
}, function (err) {
    console.error(`Next/Catch Error: '${err.message}' ...`);
}).catch((err) => {
    console.log(`Next/Catch Error: '${err.message}' ...`);
}));

/**
 * Test an async/await scenario.
 */
(async () => {
    try {
        console.log(`Return from await promise: '${JSON.stringify(await executeAsyncCallback(test2, testcb, 2, 3, 4), null, 2)}' ... `);
    } catch (err) {
        console.error(`Promise rejected: Reason = '${err.message}' ...`);
    }
})();




在示例中,当在使用参数 (2,3,4) 调用“test2”的异步方法内部时,抛出异常 Error。在标准的“promise -> then -> catch”场景中,它 returns 和 Object 是安全的。切换 'testcb' 中的逻辑以反转 success/failure 甚至更改条件 if (input === 5) 以始终成功或始终失败以查看不同的结果。

反正这是我的看法~:)