我可以在 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 行简单代码。
不混合普通回调和承诺的原因
- 首先,所有首先使用 promises 而不是普通回调的原因。我不会重复所有这些,因为它们已经被分析和重复了很多次。引入任何简单的回调只会否定很多首先使用 promises 的原因。
- 100% 基于 Promise 的代码通常会比两者的任何混合更紧凑和更简单(一旦您了解了 Promise 以及如何最好地编写代码)。
- 当您混合使用 promise 和普通回调时,控制流会变得非常复杂。每个人都有一个如何做的模型。一旦你有多个并行或顺序异步操作,普通回调就会复杂得多(这就是为什么当我们只有普通回调时必须存在诸如
async
库之类的库的原因)而这种级别的流控制是自然的并且内置承诺。然后,尝试混合控制流和错误处理的两种模型,事情很快就会变得复杂起来。
- 许多开发人员在尝试混合使用两者时在错误处理方面犯了错误,未能正确传播错误。出现这些错误的原因是,当你混音的时候,做正确的事情会更复杂。
- Promises 是 Javascript/nodejs 中异步编程的现在和未来。我们甚至很快就会拥有顶级
await
,甚至可能甚至是基于承诺的异步导入。为什么要使用不再被视为语言的现在或未来的旧模型来增加复杂性。
- 如果您有一些不 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)
以始终成功或始终失败以查看不同的结果。
反正这是我的看法~:)
我在互联网上查找了答案,但我找不到我要找的东西。
我想知道下面的代码有什么问题(前提是我们应该避免混淆回调和承诺)-
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 行简单代码。
不混合普通回调和承诺的原因
- 首先,所有首先使用 promises 而不是普通回调的原因。我不会重复所有这些,因为它们已经被分析和重复了很多次。引入任何简单的回调只会否定很多首先使用 promises 的原因。
- 100% 基于 Promise 的代码通常会比两者的任何混合更紧凑和更简单(一旦您了解了 Promise 以及如何最好地编写代码)。
- 当您混合使用 promise 和普通回调时,控制流会变得非常复杂。每个人都有一个如何做的模型。一旦你有多个并行或顺序异步操作,普通回调就会复杂得多(这就是为什么当我们只有普通回调时必须存在诸如
async
库之类的库的原因)而这种级别的流控制是自然的并且内置承诺。然后,尝试混合控制流和错误处理的两种模型,事情很快就会变得复杂起来。 - 许多开发人员在尝试混合使用两者时在错误处理方面犯了错误,未能正确传播错误。出现这些错误的原因是,当你混音的时候,做正确的事情会更复杂。
- Promises 是 Javascript/nodejs 中异步编程的现在和未来。我们甚至很快就会拥有顶级
await
,甚至可能甚至是基于承诺的异步导入。为什么要使用不再被视为语言的现在或未来的旧模型来增加复杂性。 - 如果您有一些不 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)
以始终成功或始终失败以查看不同的结果。
反正这是我的看法~:)