Skip to content

Why does flushPromises work the way that it does #11

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

Closed
Dru89 opened this issue Mar 21, 2018 · 5 comments
Closed

Why does flushPromises work the way that it does #11

Dru89 opened this issue Mar 21, 2018 · 5 comments
Labels
documentation A docs improvement is needed.

Comments

@Dru89
Copy link

Dru89 commented Mar 21, 2018

From https://twitter.com/Dru89/status/976472959345815552

I've seen examples before where flushPromises is written basically the way that you say:

const scheduler = typeof setImmedate === 'function' ? setImmediate : setTimeout;

export function flushPromises() {
  return new Promise(res => scheduler(res));
}

What I don't really understand is why this actually works. I'm assuming it has something to do with scheduling and how JavaScript actually handles asynchronous published behavior.

And relatedly:
Is this a "hack" that only works because of an implementation detail in Node/V8? (Would this break in a browser or in something like Chakra?) If it's not something in the spec, is it something that's likely to change?

@gnapse
Copy link
Member

gnapse commented Mar 21, 2018

Not an expert here, but I'll give it a try:

I think it works because setImmediate is set to execute the given callback immediately after the browser finishes flushing all the pending work in the queue. It says it'll execute immediately, but it's not actually like simply executing it in the same block as setImmediate function body. It is still scheduling it. It is sometimes replaced with setTimeout(fn, 0) which also does not really execute the given function synchronously, but with as little a delay as possible.

If there are promise callbacks already in javascript's queue pending to be processed at the time setImmediate is called, then these will be processed before the callback given to setImmediate is called. Therefore the new promise being generated in this library's flushPromises is guaranteed to resolve after the pending promises are resolved.

As to wether this might break in the future, if you read the setImmediate documentation it might seem so due to that warning at the beginning, but since it says that node.js supports it, guess that's ok for a test library meant to be run in a node env. Worst case scenario a polyfill can me made using setTimeout(fn, 0).

This article can help understand the even loop in general. And in particular, look for the section titled "Zero delays", which talks about setTimeout with delay zero.

@kentcdodds
Copy link
Member

That was a great explanation @gnapse! I should note also that this is only effective in your tests if you've mocked out your async requests to resolve immediately (like the axios mock we have in our tests).

I'm not worried about setImmediate breaking ever, so I don't think we have anything to worry about.

Anyone wanna add this to the FAQ?

@gnapse
Copy link
Member

gnapse commented Mar 21, 2018

@kentcdodds I'll do it!

I too thought about this being based on ajax requests being mocked, as they otherwise would not be immediately in the queue. I forgot to mention it though. Thanks for clarifying that.

@kentcdodds kentcdodds added the documentation A docs improvement is needed. label Mar 21, 2018
@Dru89
Copy link
Author

Dru89 commented Mar 22, 2018

Thanks all! This tracks with what I assumed, but it's good to see it written down and I think talking about why it worksin libs that expose it is probably good. :)

Also the bit about async stuff needing to resolve immediately bis pretty important. I was somehow assuming that it would it would work for all async calls but had no clue how. I suppose for end to end testing, you'd probably still need some way to wait for whatever result you were looking for to occur. (Element on the page, row change in a database, etc.)

@gnapse
Copy link
Member

gnapse commented Mar 22, 2018

For end-to-end tests, check out cypress. It has an amazing way to declare the network requests you expect to occur in your test, and then declaratively wait for them to resolve before continuing the test.

In that case the network requests can either be mocked or not, and the test will work anyway.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
documentation A docs improvement is needed.
Projects
None yet
Development

No branches or pull requests

3 participants