0

I was looking at this mutation obersver in some typescript code and I can’t figure how the promise works in it, I’ve not see a promise without an argument in this style before:

  const observer = new MutationObserver((mutations: MutationRecord[]) => {
    Promise.resolve().then(someNextMethod)
    this.somethingThatTakesAWhile()
  })

  observer.observe(HTMLtarget)

When the observation is triggered the someNextMethod runs after this.somethingThatTakesAWhile() has ran. How is this possible in this instance? I don't understand how the Promise get passed any arguments and knows how to resolve in this case. Could someone explain the internal mechanics of this code please? I'm at a bit of a loss as to how it runs in that order. Thanks!

CafeHey
  • 5,699
  • 19
  • 82
  • 145
  • 2
    the promise does nothing, but forces the code to wait asynchronously to "resolve" that promise. It's basically a Promise version of the old `setTimeout(doSomething, 0);` hack. – Robin Zigmond Feb 04 '23 at 17:36
  • Here is a great talk explaining how these types of event loop tricks work in JavaScript: https://www.youtube.com/watch?v=cCOL7MC4Pl0 – David Huculak Feb 04 '23 at 17:37
  • 1
    @RobinZigmond Not quite, the promise version will place it on the microTask queue.. – Keith Feb 04 '23 at 17:50
  • 1
    @Keith - yes I'm aware of that, I just decided it wasn't worth elaborating on that difference in a comment. But it was in my mind when I said "a Promise version of". – Robin Zigmond Feb 04 '23 at 17:50
  • A little off-topic, but the `this.somethingThatTakesAWhile()` doesn't sound like something that should be executed on the main thread. Running blocking code freezes the browser until it finishes. – FZs Feb 04 '23 at 18:07
  • Sorry for the confusion, but none of this explains why the code waits for `somethingThatTakesAWhile` to finish before `someNextMethod` is ran, that's the part I'm having difficulty understanding, why does `somethingThatTakesAWhile` run and finish first? – CafeHey Feb 04 '23 at 21:57
  • Relevant: https://stackoverflow.com/questions/27647742/promise-resolve-then-vs-setimmediate-vs-nexttick – Bergi Feb 04 '23 at 22:46
  • 1
    @CafeHey Because until all synchronous code has finished, `someNextMethod` won't have a chance to execute. The method will get pushed into a queue and before the next event queue is processed it will execute whats in the micro task queue. – Keith Feb 05 '23 at 10:45

2 Answers2

1

The main point of something like this:

 Promise.resolve().then(someNextMethod)

is just to call someNextMethod() after the current chain of execution finishes. In a browser, it is similar to this:

setTimeout(someNextMethod, 0);

though the Promise.resolve() method will prioritize it sooner than setTimeout() if other things are waiting in the event queue.


So, in your particular example, the point of these two statements:

Promise.resolve().then(someNextMethod)
this.somethingThatTakesAWhile()

is to call someNextMethod() after this.somethingThatTakesAWhile() returns and after the MutationObserver callback has returned, allowing any other observers to also be notified before someNextMethod() is called.


As to why this calls someNextMethod() later, that's because all .then() handlers run no sooner than when the current thread of execution completes (and returns control back to the event loop), even if the promise they are attached to is already resolved. That's how .then() works, per the Promise specification.


Why exactly someone would do that is context dependent and since this is all just pseudo-code, you don't offer any clues as to the real motivation here.

jfriend00
  • 683,504
  • 96
  • 985
  • 979
  • thank you so much @jfriend00, in this context it was to disconnet the observer until the code had ran and then reconnect it, in case `somethingThatTakesAWhile()` triggered the observer and caused an infinite loop. – CafeHey Feb 05 '23 at 11:49
0

This:

 Promise.resolve().then(someNextMethod)

Is equivalent to this:

 Promise.resolve().then(() => someNextMethod())

Working backward is equivalent to:

const myNewMethod = () => someNextMethod()
Promise.resolve().then(myNewMethod)

Defining a function inline or pointing to a function is just a syntactical difference. All of the arguments passed through then will be passed to the referenced function. But in this case, there isn't any as it's a promise with an empty return value.

In other words, the method doesn't need any parameters. In this instance it's actually just an old JS hack/trick to get the code to run at the end of the call stack.

adsy
  • 8,531
  • 2
  • 20
  • 31
  • "*there isn't any*" - actually there is, the value `undefined`. A promise cannot have an "empty" result. – Bergi Feb 04 '23 at 19:13
  • Sorry, but that doesn't bring `somethingThatTakesAWhile` into the flow, how come `somethingThatTakesAWhile` runs before `someNextMethod`? – CafeHey Feb 04 '23 at 21:54
  • Because promises are asynchronous. Code in JS does not necessarily execute in order. Asynchronous code is code which is queued for future computation. Usualy its used for things like network requests. Lets just pretend this is one for the sake of argument. With a promise that represents a network request, the callback would run when the response comes back. In your example, the callback is queued straightaway because there's nothing to wait for. – adsy Feb 04 '23 at 23:09
  • You might wonder why it then happens before. The reason is that in JS, all promises are queued at the end of the current call stack/event loop. Its still queued, even if it is queued for execution "as soon as possible", in the context of async programming, that is still at the end of the current event loop/call stack – adsy Feb 04 '23 at 23:09