Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Proposal: Fix async functions not executed in time when ticking a fake clock forward #2452

Open
Zomono opened this issue Apr 19, 2022 · 1 comment
Labels

Comments

@Zomono
Copy link

Zomono commented Apr 19, 2022

Is your feature request related to a problem? Please describe.

I had some trouble with async function calls submitted to setTimeout/setImmediate/setInterval when using faked timers, and it seems that I am not the only one. The problem is that methods like tick and even tickAsync do sometimes return even before the scheduled callbacks have finished.
So it is not possible to have a reproducible state after e.g. tickAsync has returned.

Describe the solution you'd like

I have noticed that there are some plans to mock or stub promises completely, but for now I propose to implement a short workaround that may cover most of the use cases I can imagine:

It should be quite easy for users to make all of their callbacks from the production code returning a promise that is resolved or rejected exactly when the purpose of the callback has been done. As it is a best practice to not have unhandled promises, this may already been the case in clean implementations, even if the original APIs for setTimeout and friends do not expect promises as return types.

So assuming that, you may implement a queue of promises, that is filled with the promises of all callbacks (or their normal return values wrapped as a promise).

    const promiseQueue = new Set() // global or one instance per InstalledClock
    const wrappedCallback = (...args: any[]) => {
        const promise = Promise.resolve(callback(...args))
        promiseQueue.add(promise)
        promise.finally(() => promiseQueue.delete(promise))
    }
  // call internal setTimeout/setImmediate/setInterval with the wrapped callback

The methods tickAsync, runToLastAsync, runAllAsync and nextAsync would than wait for all promises of that queue when called and clear the queue afterwards.

Depending on the used tick-Function (e.g. runAllAsync) you may need to check'n'wait for the promise queue multiple times.

Things to consider

A promise from a user's callback may depend on the execution of other callbacks submitted by e.g. setImmediate. So while waiting for such promises to resolve/reject,
the execution of callbacks from setImmediate, setTimeout(0), etc. should not be blocked.

If the user's callback returns a promise that depends on future executions like setTimeout(100) and the user awaits the result of e.g. tickAsync at the same time, waiting infinite long should be the expected behaviour.

Benefits

Using this workaround all the async tick functions won't return to early anymore,
unless there is a bug in the users promise chain, which is something they probably want to fix anyway.

Copy link

stale bot commented Dec 27, 2023

Is this still relevant? If so, what is blocking it? Is there anything you can do to help move it forward?

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs.

@stale stale bot added the wontfix label Dec 27, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

1 participant