等待/异步如何处理未解决的承诺

Posted

技术标签:

【中文标题】等待/异步如何处理未解决的承诺【英文标题】:await/async how to handle unresolved promises 【发布时间】:2020-04-21 17:18:53 【问题描述】:

你如何处理无法解决的承诺?

例子:

class Utils 
    static async thisFunctionOnlyResolvesWhenPassed2AndNeverRejects(number: number) 
        return new Promise((resolve, reject) => 
            if(number === 2) 
                resolve('ok')
            
        )
    


console.log(await Utils.thisFunctionOnlyResolvesWhenPassed2AndNeverRejects(2))
// this will print "ok" because 2 is passed and the promise is resolved

console.log(await Utils.thisFunctionOnlyResolvesWhenPassed2AndNeverRejects(5))
// this will crash the program silently 

uncaughtExceptionunhandledRejection 在 promise 未解决时不返回任何内容。在 await 周围添加 try/catch 不起作用(没有错误)。最后,唯一可行的是使用Promise.then 而不是await

问题是代码库充斥着async/await 和有时会解决的承诺(取决于条件)

问题:我可以添加一个打字稿标志来检测丢失的解析/拒绝吗?或者可能是一种将所有async/await 转换为使用Promise.then 的自动方式?

使用调试器时,程序在 Promise 之后停止,很难找到哪个函数/promise 缺少解析/拒绝。

重写所有async/await 调用以使用Promise.then 是我最后的手段。

【问题讨论】:

问题是:代码库中充斥着 async/await 和有时会解决的 Promises 这是代码库需要解决的问题。虽然有一些方法可以绕过它(例如,等待该 Promise 和 60 秒后拒绝的 Promise 的 Promise.race),但您不应该在消费者中这样做 - 修复为您提供的功能相反,这些 Promise 基本上都被打破了 return new Promise((resolve, reject) => if(number === 2) resolve('ok') else resolve('not ok') )跨度> @CertainPerformance 你知道发生这种情况时我如何获得堆栈跟踪或错误吗?让我生气的是,这一切都在悄无声息地发生。我们的错误日志是空的,因为节点没有报告任何内容。我正在考虑包装整个程序并返回退出代码以检测它。如上所述,代码库中到处都是它,我需要在重构所有这些承诺之前确定问题的大小。谢谢! 对于一般情况,不可能从外部知道 Promise 是永远挂起还是最终会解决(或拒绝)。修复基础代码使其不会从根本上被破坏应该是第一步,而不是第二步 【参考方案1】:

如果您的承诺偶尔不会解决或拒绝,并且这不是它们应该工作的方式(通常不是),那么您只需要解决它。真的没有变通办法。正确的解决方法是降低到最低级别并修复代码,以便每次都能可靠地解析或拒绝。


这不是正确的解决方法,但实现超时包装器可以帮助调试,为您提供一条日志消息,其中包含一些类似堆栈跟踪的超时承诺:

function rejectT(t) 
    // create potential error here for better opportunity at stack trace
    let e = new Error("Promise timed out");
    return new Promise((resolve, reject) => 
        setTimeout(() => 
            console.log(e);
            reject(e);
        , t);
    );


function timeout(p, t = 5000) 
    return Promise.race([p, rejectT(t)]);

然后您可以包装任何承诺,而不是:

fn().then(...).catch(...)

你可以使用:

timeout(fn()).then(...).catch(...);

或者,如果您想设置自定义超时值:

timeout(fn(), 1000).then(...).catch(...);

再次强调,这是调试代码,以帮助找到需要修复的罪魁祸首并帮助测试修复,而不是对您的代码进行创可贴。

重写所有 async/await 调用以使用 Promise.then 是我最后的选择。

我完全看不出这有什么帮助。如果await 永远不会完成,promise.then() 也不会。在这方面它们完全相同。如果承诺永远不会解决或拒绝,那么.then() 处理程序也永远不会被调用。

问题是代码库充满了异步/等待和有时会解决的承诺(取决于条件)

除了有条不紊地进行代码审查之外,这里没有捷径可走,可以找到具有可能永远无法解析或拒绝的代码路径的可疑代码,然后构建单元测试来测试在各种条件下返回承诺的每个函数。

从不解析或拒绝的一个可能的代码来源是一些 Promise 反模式。其中一些是反模式的确切原因是因为它们很容易搞砸。以下是一些可能会提高您对可疑代码敏感度的参考资料:

Promise Anti-Patterns

Common Promise Anti-Patterns and How to Avoid Them

ES6 Promises: Patterns and Anti-Patterns

【讨论】:

我已经在使用 tslint 并且开启了“promise”规则;你知道是否有另一个 linter 能够为我检测这些反模式?到目前为止,我正在考虑记录请求和一些退出代码;没有退出代码的表示程序意外终止 @hbt - 我不知道检测承诺反模式的 linter。在已知函数返回承诺的情况下,使用 TypeScript 会更可行。 仅供参考:找到了一种方法来检测使用异步钩子nodejs.org/api/async_hooks.html 无法解析/拒绝的承诺——我可以识别哪些请求产生未解决的承诺然后修复它们。我使用 codemod 在 await 调用周围添加钩子,然后运行测试套件和看起来有问题的旧请求,以查找哪些承诺被破坏。谢谢! @hbt - 很酷。我猜是 node.js 中的一些相对较新的东西。【参考方案2】:

async function thisFunctionOnlyResolvesWhenPassed2AndNeverRejects(number) 
  return new Promise((resolve, reject) => 
    if (number === 2) 
      resolve('ok')
     else 
      reject('error:' + number)
    
  )

(async() => 
  try 
    console.log(await thisFunctionOnlyResolvesWhenPassed2AndNeverRejects(2))
    // this will print "ok" because 2 is passed and the promise is resolved
   catch (e) 
    console.error(e);
  
  try 
    console.log(await thisFunctionOnlyResolvesWhenPassed2AndNeverRejects(5))
    // this will crash the program silently
   catch (e) 
    console.error(e);
  
)()

【讨论】:

这不是真正的问题。 OP 只是展示了一个不可靠的示例承诺。真正的问题在于 OP 可能没有编写的一些其他代码,但正在尝试修复使用这些不可靠承诺的代码。

以上是关于等待/异步如何处理未解决的承诺的主要内容,如果未能解决你的问题,请参考以下文章

如何处理未定义的 Observable

swift - 如何处理未捕获的异常

如何处理未提交的更改

如何处理未打开的推送通知(iOS、Swift)

如何处理未捕获的错误:对象作为 React 子错误无效

在Angular中使用Input传递数据时如何处理未定义?