不解决不重要的承诺是一种不好的做法吗?

3
我正在努力理解一种实践,我现在在不同的代码库中注意到函数离开了未解决的辅助异步函数调用。
我认为这样做可能是为了节省性能,因为等待承诺被解决可能会增加额外的开销,而没有真正的好处(如果承诺对函数的结果不重要),除了保证一切都已经完成。
我经常看到的一个常见例子是缓存。
async function someFunction() {

  const result = await doSomething();
  
  cacheResult(result);
  
  return result;
}

async function cacheResult(someValue) {
  // ... cache logic returning a promise
}

在上面的例子中,我是否正确地假设存在这样一种情况,即在cacheResult被解析之前,someFunction已经完成了?如果是这样,那么我假设cacheResult将在“稍后的某个任意时间”解析?
如果我将someFunction包裹在一个try/catch块中,会发生什么呢?
try {
  await someFunction();
} catch (error) {
  // ... error handling
}

但是cacheResult的promise在稍后会解析为拒绝。即使我将其包裹在try/catch中,这会导致未处理的promise拒绝并终止进程吗?
假设对cacheResult进行了适当的错误处理,并且它是一个耗时较长的复杂函数,那么将其解析为拒绝并继续处理以不损失性能是一种有效的做法吗?

你是在问关于永远不解决承诺(使其保持待定状态),还是在问关于永远不等待承诺(忽略其结果)? - undefined
1
这会导致一个未处理的 Promise 拒绝并终止进程吗?是的。所以不要这样做。当你结束一个 Promise 链时,总是要添加错误处理。 - undefined
2个回答

3
我是否正确地假设,在上述示例中,存在这样一种情况,即在cacheResult解析之前,someFunction已经完成了?
事实上,这几乎是肯定的。cacheResult会立即执行,但它还没有解析(除非你没有进行任何异步工作并立即返回)。举个例子来说明:
async function doSomething() {
  return 1;
}

async function someFunction() {

  const result = await doSomething();

  cacheResult(result).then(console.log);

  return result;
}

async function cacheResult(someValue) {
  await null; // have this function take at least one tick.
  return 2;
}

someFunction().then(console.log);
// Logs 1, then 2.

如果是这样的话,我认为cacheResult将在“以后的某个任意时间”解决,对吗?
是的。
事实上,如果cacheResult是这样实现的,它甚至根本不会解决:
function cacheResult(someValue) {
  return new Promise((resolve, reject) => {
    // don't do anything with resolve() or reject()
  });
}

是的。这将无法捕获cacheResult调用中的异常,并导致未处理的Promise拒绝。
// Make it throw an error:
async function cacheResult(someValue) {
  await null; // have this function take at least one tick.
  throw new Error('Custom error');
}
// Run Node with warnings enabled:
// node --unhandled-rejections=warn example.js
// UnhandledPromiseRejectionWarning: Unhandled promise rejection. 
// This error originated either by throwing inside of an async 
// function without a catch block, or by rejecting a promise which 
// was not handled with .catch(). (...)

错误消息实际上指向了这个答案。
假设对于cacheResult已经有了适当的错误处理,并且它是一个耗时较长的复杂函数,那么将其留给自行解决并继续处理以不损失性能是一种有效的做法吗?
是的,完全可以。
async function someFunction() {

  const result = await doSomething();

  cacheResult(result).catch(err => console.warn("cacheResult:", err));

  return result;
}

你可能想考虑添加一个机制,确保一个慢的cacheResult()调用不会覆盖一个较新的cacheResult(),后者可能更早地完成(例如保存一个时间戳或修订号)。

2
是的,拥有永远挂起的承诺是不好的做法,因为它们占用资源。
但是在你的示例中没有(明显的)未解决的承诺。仅仅因为你没有等待一个承诺,这并不意味着它不会解决(假设cacheResult最终会完成)。如果你现在不关心承诺的结果,不等待承诺是完全可以的。
但是,是的,你的代码会抛出一个未处理的承诺拒绝,即使你将await someFunction()包装在try {...} catch {...}块中,因为你失去了由cacheResult返回的承诺的上下文。也就是说,在cacheResult返回的承诺解决或拒绝之前,someFunction()中的异步代码早就完成了(它返回的承诺早就解决或拒绝了)。
所以,如果你不想等待cacheResult,或者你不关心它是否成功,可以通过以下方式使其在任何情况下都不抛出异常:
async function cacheResult(foo) {
  try {
    //some async code
  } catch {
    //optional error handling/logging
  }
}

或者在调用时添加一个拒绝处理程序
async function someFunction() {
   //some code
   cacheResult(...).catch(e => {
     //optional error handling/logging
   })
   //some more code
}

这两种变体都会稍后完成cacheResult,但仍然会捕获它抛出的任何错误。
但要注意一件事:例如,Node.js可能不会等待未决的承诺并终止,因此如果没有其他事情保持进程活动,您的cacheResult函数可能随时被终止...

因为它们占用资源 - 实际上并不占用资源,如果它们的解析器函数被垃圾回收并且它们自身没有被引用 - undefined

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