关于3.8中resolveAfterPromiseResolved和rejectAfterPromiseResolved错误

cocos 3.8的版本中unhandleRejectedPromise和handlerAddedAfterPromiseRejected的promise官方已经修复,但是还存在大量的resolveAfterPromiseResolved和rejectAfterPromiseResolved的错误。这两种错误只要使用promise.race和promise.all或者多次resolve都会触发,看了引擎源码,错误处理是基于v8的SetPromiseRejectCallback方法,对于kPromiseRejectAfterResolved和kPromiseResolveAfterResolved类型的错误直接输出到标准错误流中了。想问下这样的处理是否合适?同时我也查阅了node社区和源码,node中也是基于SetPromiseRejectCallback实现,也同样会报错,这个本身应该是v8的bug?但是node中是监听multipleResolves事件的方式,并且该事件也已经弃用。可以参考
https://github.com/nodejs/node/issues/24321和https://github.com/nodejs/node/pull/41872。
所以想问下官方,现在这个promise类型的错误是否真的有意义?如果一定要存在的话,又是否必要输出到标准错误流中。
image
image

@boyue 可以帮忙看下这个问题吗?

@68637552 @boyue 以帮忙看下吗

你好,感谢反馈问题。

如果没有在代码中 catch reject 的 promise,是会在日志中输出错误信息。

你这里可否提供一些代码片段或者简单复现问题的 Demo 工程?
并且描述一下

  1. 当前你觉得错误的行为
  2. 你觉得正确的行为,比如:web 或者 node 上运行这段代码没有问题,但是原生上却报了一堆错误日志

另外,3.8.5 中,我们会参考 node 和 chromium 的实现对 Promise unhandled rejection 的处理流程做一些优化工作。

node代码一:即使catch,SetPromiseRejectCallback也会触发kPromiseResolveAfterResolved类型的错误

function test() {
 return new Promise(r => {
   r('first');
   setTimeout(() => {
     r('second');
   });
 });
}
async function main() {
 try {
   await test();
 } catch (error) {
   console.log('error===', error)
 }
}

main();

// multipleResolves这个实现也是基于SetPromiseRejectCallback,和业务catch没有关系
process.on('multipleResolves', (type, promise, reason) => {
 console.error('Promise was settled multiple times:', type, promise, reason);
 // 可以在这里添加错误处理逻辑
});

image

node代码二:使用promise race或者all,SetPromiseRejectCallback也会触发kPromiseResolveAfterResolved类型的错误

function test1() {
  return new Promise(r => {
    setTimeout(() => {
      r('first=');
    }, 1000);
  });
}

function test2() {
  return new Promise(r => {
    setTimeout(() => {
      r('second=');
    }, 2000);
  });
}
async function main() {
  try {
    const p = await Promise.race([test1(), test2()])
    console.log('p===', p)
  } catch (error) {
    console.log('error===', error)
  }
}

main();

// multipleResolves这个实现也是基于SetPromiseRejectCallback,和业务catch没有关系
process.on('multipleResolves', (type, promise, reason) => {
  console.error('Promise was settled multiple times:', type, promise, reason);
  // 可以在这里添加错误处理逻辑
});

image

@dumganhar
问题1:即使代码中 catch reject 的 promise,也会在日志中输出错误信息,demo工程可以直接在node里运行
问题2: 当前node已经废弃了multipleResolves事件,是不是意味着这个类型的问题不应该以错误的形式输出。
企业微信截图_0a356d00-6a4d-45a9-b209-1811c750d42e
企业微信截图_ebecfb5c-a9da-414d-893f-ee6d9ce3a568

我觉得正确的做法是屏蔽或者以事件监听的方式,而不是直接输出错误流
image

嗯,catch 的的确不能再输出。这个我跟进一下。

目前我们并没有实现 multipleResolves 回调,我得看下 node 现在多次 resolve 的行为结果是怎么样。尽量保持跟 node 和 web 一致的行为。

另外,感谢提供详细的信息。

1赞

v8 shell (d8) 中如果收到 kPromiseResolveAfterResolved ,是直接返回的。我计划也这样处理。

1赞

创建一个 issue 在 3.8.5 中跟进: https://github.com/cocos/cocos-engine/issues/17051

1赞

所以结论是可以忽略kPromiseRejectAfterResolved和kPromiseResolveAfterResolved类型,并且不会输出到标准错误流中吗,我们准备修改引擎,把下图的部份注释掉,可以帮忙看下这样处理ok吗
image

嗯,最简单改法,就是先忽略kPromiseRejectAfterResolved和kPromiseResolveAfterResolved类型

想在资讯个问题,我们引擎2.4的,针对kPromiseRejectWithNoHandler和kPromiseHandlerAddedAfterReject我们参考了3.8的改法,但是调用handlePromiseExceptions的时候,发现3.8和2.4的差异很大,3.8实现了一套tick。2.4就是一个SimpleRunLoop,所以想问下官方,可以直接在image jsb_global.ccp的update里调用handlePromiseExceptions来处理v8的promise错误吗。这里这么改也是主要解决unhandledRejectedPromise和handlerAddedAfterPromiseRejected会在同步的一个promise中连续触发。

看2.x 的逻辑是有一些区别的,2.x 中并没有 ScriptEngine::handlePromiseExceptions 函数。他在处理
onPromiseRejectCallback 回调的时候直接触发 callExceptionCallback 回调。

你直接在 onPromiseRejectCallback 回调中,判断如果是 kPromiseRejectAfterResolved和kPromiseResolveAfterResolved类型 就直接返回。

是另一个问题,下面这个promise在2.4中会连着触发2次onPromiseRejectCallback,一次是kPromiseRejectWithNoHandler,另一次是kPromiseHandlerAddedAfterReject。2.4中是直接输出错误,所以针对下面这个正常的promise会连着吐出2次错误,3.8中修复了这个问题,是在一个tick中判断有一对kPromiseRejectWithNoHandler和kPromiseHandlerAddedAfterReject,就认为这个promise是正常的。但是2.4中没有tick的概念,我参考3.8处理了这个promise,但是3.8是在tick中调用的handlePromiseExceptions,想问下2.4该怎么调用handlePromiseExceptions?在什么时机?
image

function test() {
return new Promise(resolve => {
setTimeout(() => {
resolve(true);
});
});
}
test()
.then(() => {})
.catch(error => {});

我刚 review 了一下逻辑,在 SimpleLoop 里面加行不通,SimpleRunLoop 应该永远不会被执行的。

由于 2.x 与 3.x 平台抽象层的差异,目前你最简单的改法应该是直接在 EventDispatcher::dispatchTickEvent 的最后,加上你新加的 handlePromiseExceptions()

或者你搜索代码,在所有调用 EventDispatcher::dispatchTickEvent() 之后的逻辑中去触发 handlePromiseExceptions。

收到,我试一下,感谢

我这边改完验证过是ok的,可以给2.4版本提一个mr吗?解决promise相关的问题

可以提一下,不过现在 2.x 发版周期相对比较长。你可以先提交,我们再评估一下在 2.x 哪个版本中去合并。谢谢。

我们在灰度期间的时候,发现会偶现crash,具体的crash信息如下。


想问下为什么同一时间触发了2次EventDispatcher::dispatchTickEvent呢。
@dumganhar

???

2 次 handlePromiseExceptions ?因为这个函数是你自己加到 2.x 引擎中的,你提交一下 PR,我看下具体的改动点。