使用 sinon 时出现不一致的 UnhandledPromiseRejectionWarning

Posted

技术标签:

【中文标题】使用 sinon 时出现不一致的 UnhandledPromiseRejectionWarning【英文标题】:Inconsistent UnhandledPromiseRejectionWarning when using sinon 【发布时间】:2019-03-11 07:03:44 【问题描述】:

据我所知,failApiClientexplicitFailApiClient 这两个对象应该具有相同的类型,并且记录它们似乎同意:

console.log(failApiClient) // getObjects: [Function: getObjects] console.log(explicitFailApiClient) // getObjects: [Function: getObjects]

阅读this 问题为我提供了正确处理此问题所需的信息,但它并没有告诉我为什么生成的failApiClient 会导致警告,而explicitFailApiClient 不会。

我已将其缩减到接近重现环境和展示可行替代方案所需的最低要求:

import * as sinon from 'sinon';
import 'source-map-support/register';

class LocalObject 


const fakeObject = new LocalObject();

const getFakeApi = (result: Promise<LocalObject[]>) = (getObjects: () => result);

const successObjectClient = getFakeApi(Promise.resolve([fakeObject]));

// These should be equivalent, but the former causes a test error
const failApiClient = getFakeApi(Promise.reject(new Error()));

const explicitFailApiClient = 
  getObjects(): Promise<LocalObject[]> 
    return Promise.reject(new Error());
  
;

describe('successApiClient', () => 
  before(() => 
    sinon.spy(successObjectClient, 'getObjects');
  );

  it('does not have a warning', async () => 
    // do nothing
  );

);

describe('failApiClient', () => 
  before(() => 
    sinon.spy(failApiClient, 'getObjects');
  );

  it('should not have a warning', async () => 
    // do nothing
  );
);

describe('explicitFailApiClient', () => 
  before(() => 
    sinon.spy(explicitFailApiClient, 'getObjects');
  );

  it('does not have a warning', async () => 
    // do nothing
  );
);

还有~/...&gt; tsc &amp;&amp; npm test的结果:

> internal-api@1.0.0 test /Users/./Projects/./node/internal-api
> grunt test

Running "test" task

Running "env:dev" (env) task

Running "simplemocha:unit" (simplemocha) task


(node:72101) UnhandledPromiseRejectionWarning: Error
    at Object.<anonymous> (/Users/./Projects/./node/internal-api/src/test/unit/models/mvp.test.ts:21:57)
    at Module._compile (module.js:652:30)
    at Object.Module._extensions..js (module.js:663:10)
    at Module.load (/Users/./Projects/./node/internal-api/node_modules/coffee-script/lib/coffee-script/register.js:45:36)
    at tryModuleLoad (module.js:505:12)
    at Function.Module._load (module.js:497:3)
    at Module.require (module.js:596:17)
    at require (internal/module.js:11:18)
    at /Users/./Projects/./node/internal-api/node_modules/mocha/lib/mocha.js:222:27
    at Array.forEach (<anonymous>)
    at Mocha.loadFiles (/Users/./Projects/./node/internal-api/node_modules/mocha/lib/mocha.js:219:14)
    at Mocha.run (/Users/./Projects/./node/internal-api/node_modules/mocha/lib/mocha.js:487:10)
    at Object.<anonymous> (/Users/./Projects/./node/internal-api/node_modules/grunt-simple-mocha/tasks/simple-mocha.js:29:20)
    at Object.<anonymous> (/Users/./Projects/./node/internal-api/node_modules/grunt/lib/grunt/task.js:255:15)
    at Object.thisTask.fn (/Users/./Projects/./node/internal-api/node_modules/grunt/lib/grunt/task.js:73:16)
    at Object.<anonymous> (/Users/./Projects/./node/internal-api/node_modules/grunt/lib/util/task.js:294:30)
    at Task.runTaskFn (/Users/./Projects/./node/internal-api/node_modules/grunt/lib/util/task.js:244:24)
    at Task.<anonymous> (/Users/./Projects/./node/internal-api/node_modules/grunt/lib/util/task.js:293:12)
    at /Users/./Projects/./node/internal-api/node_modules/grunt/lib/util/task.js:220:11
    at _combinedTickCallback (internal/process/next_tick.js:131:7)
    at process._tickCallback (internal/process/next_tick.js:180:9)
    at Function.Module.runMain (module.js:695:11)
    at startup (bootstrap_node.js:191:16)
    at bootstrap_node.js:612:3
(node:72101) 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(). (rejection id: 1)
(node:72101) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.
  successApiClient
    ✓ does not have a warning

  failApiClient
    ✓ should not have a warning
(node:72101) PromiseRejectionHandledWarning: Promise rejection was handled asynchronously (rejection id: 1)

  explicitFailApiClient
    ✓ does not have a warning


  3 passing (14ms)


Done.

【问题讨论】:

【参考方案1】:

它们不等价。

在下面的代码中,当 Promise.reject 作为参数时,JS 已经执行了语句,这就是为什么你会收到早期警告 UnhandledPromiseRejectionWarning

const failApiClient = getFakeApi(Promise.reject(new Error()));

对比

const explicitFailApiClient = 
  getObjects(): Promise<LocalObject[]> 
    return Promise.reject(new Error());
  
;

在调用explicitFailApiClient.getObjects() 时将评估其Promise.reject

解决方案

这是我针对此问题的替代解决方案。我可以使用来自诗乃的resolvesrejects

const getFakeApi = getObjects: (result) => result;
const getFakeApiStub = sinon.stub(getFakeApi, 'getObjects');

describe('successApiClient', () => 
  before(() => 
    getFakeApiStub.resolves([fakeObject]); // success and resolves
  );

  it('does not have a warning', async () => 
    // do nothing
  );

);

describe('failApiClient', () => 
  before(() => 
    getFakeApiStub.rejects(new Error()); // make it failed
  );

  it('should not have a warning', async () => 
    // do nothing
  );
);

参考: https://sinonjs.org/releases/v6.3.5/stubs/#stubresolvesvalue

希望对你有帮助

【讨论】:

【参考方案2】:

Promise 可以是未解决、已解决或拒绝。 “then”用于处理“catch”处理拒绝的解决方案。你在没有抓住它的情况下抛出拒绝。

试试SomePromiseRejection().catch(err =&gt; DoSomeStuff(err))

所以在explicitFailApiClient 被调用后有一个.catch 块。

【讨论】:

explicitFailApiClient 不会导致UnhandledPromiseRejectionWarning - 只有failApiClient 会。这就是我试图理解的症结所在。

以上是关于使用 sinon 时出现不一致的 UnhandledPromiseRejectionWarning的主要内容,如果未能解决你的问题,请参考以下文章

使用 gprof 分析我的代码时出现不一致

PySpark - 运行 Count() / 聚合函数(平均等)时出现不一致

从引用游标中获取批量收集时出现不一致的数据类型错误

使用 %s 时出现不支持的操作数类型错误

构建我的应用程序时出现不推荐使用的错误

尝试子查询时出现不明确的列名错误