question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. ItĀ collects links to all the places you might be looking at while hunting down a tough bug.

And, if youā€™re still stuck at the end, weā€™re happy to hop on a call to see how we can help out.

Fake timers behave unexpectedly with `Promise.race()`.

See original GitHub issue

šŸ› Bug Report

With jest.useFakeTimers (either ā€˜modernā€™ or ā€˜legacyā€™), a Promise.race between a short sleep Promise and a long sleep Promiseā€¦resolves to the long oneā€™s resolution, if the short one uses .then.

An identical Promise.race using real timers behaves as expected.

To Reproduce

For example, with modern fake timers (but also observed with the legacy fake timers):

describe('modern fake timers', () => {
  jest.useFakeTimers('modern');

  test('Promise.race without "then"', async () => {
    const shortPromise = new Promise(resolve => setTimeout(() => resolve('short'), 1000));
    const longPromise = new Promise(resolve => setTimeout(() => resolve('long'), 2000));
    
    const racedPromise = Promise.race([shortPromise, longPromise]);

    jest.runAllTimers();
    const result = await racedPromise;

    expect(result).toBe('short');
  });

  test('Promise.race with "then"', async () => {
    const shortPromise = new Promise(resolve => setTimeout(resolve, 1000)).then(() => 'short');
    const longPromise = new Promise(resolve => setTimeout(() => resolve('long'), 2000));
    
    const racedPromise = Promise.race([shortPromise, longPromise]);

    jest.runAllTimers();
    const result = await racedPromise;
    
    // Surprise! It's 'long'.
    expect(result).toBe('short');
  });
});

The second test, ā€˜Promise.race with ā€œthenā€ā€™, fails.

Expected behavior

I would expect the second test, ā€˜Promise.race with ā€œthenā€ā€™, to succeed; the shorter sleep should ā€œwinā€ the race, same as the version without ā€œthenā€. An identical test succeeds if you do jest.useRealTimers().

Link to repl or repo (highly encouraged)

Here, we have with-ā€œthenā€ and without-ā€œthenā€ tests for each of the following:

  • Modern fake timers (surprising behavior observed)
  • Legacy fake timers (surprising behavior observed)
  • Real timers (behaves as expected)

https://github.com/chrisbobbe/jest-modern-timers-race-then

envinfo

  System:
    OS: macOS 10.15.5
    CPU: (12) x64 Intel(R) Core(TM) i7-9750H CPU @ 2.60GHz
  Binaries:
    Node: 10.20.1 - ~/.nvm/versions/node/v10.20.1/bin/node
    Yarn: 1.22.4 - /usr/local/bin/yarn
    npm: 6.14.4 - ~/.nvm/versions/node/v10.20.1/bin/npm
  npmPackages:
    jest: ^26.1.0 => 26.1.0 

Issue Analytics

  • State:closed
  • Created 3 years ago
  • Reactions:3
  • Comments:5

github_iconTop GitHub Comments

1reaction
MaxMotovilovcommented, Jan 22, 2021

Running into similar issues with multiple timers and promises. Wouldnā€™t it help to have an asynchronous version of jest.runAllTimers() ā€“ i.e. one that would setImmediate() between timer firings? This assumes promise implementation uses an equivalent of nextTick() to schedule the callback execution. Basically we need to be sure that all promise chains resolvable from a given timer would fully resolve themselves before the logically next (i.e. set to a later time) timer would fire.

By the looks of it, got to be a one-liner ā€“ @sinonjs/faketimers runAllAsync() is already available but not used by Jest.

https://github.com/facebook/jest/blob/412954fd260f17976a450bd3b252a5d7e2488a64/packages/jest-fake-timers/src/modernFakeTimers.ts#L50

Can either add runAllTimersAsync() or a boolean async parameter to the existing callā€¦ For the time being, perhaps

jest._clock.runAllAsync()

could be used as a kludge.

0reactions
github-actions[bot]commented, May 5, 2022

This issue has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs. Please note this issue tracker is not a help forum. We recommend using StackOverflow or our discord channel for questions.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Jest fake timers with promises - Stack Overflow
Since jest@26.0.0 you can choose between two different fake timer ... Date but not with Promises since await flushPromises() never resolves.
Read more >
Timeout for an async loop: if loop do not finishes before ...
Hi Team, I propose a new syntax : await[timeoutInMs] to control timeout of a promise. I have summarized this whole thread here in...
Read more >
SalesForce Javascript Developer Certfication - Quizlet
Exits early if at least 1 promise is rejected. promise.race() also accepts an array of promises, but returns the first settled promise (resolved...
Read more >
unexpected-bluebird - npm
Full featured Promises/A+ implementation with exceptionally good performance. Latest version: 2.9.34-longstack2, last published: 7 years ago.
Read more >
Changelog - Cypress Documentation
This change adds consistency around how .within() behaves across commands. ... Cypress will now throw an error when we receive an unexpected return...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found