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

batch emitting new values #2

Open
Raynos opened this issue Feb 23, 2014 · 12 comments
Open

batch emitting new values #2

Raynos opened this issue Feb 23, 2014 · 12 comments

Comments

@Raynos
Copy link
Owner

Raynos commented Feb 23, 2014

Currently we emit a new value (and do a new shallow clone) every time a key/value pair changes.

We probably want to batch this up and only emit a new value in process.nextTick and do a shallow clone once.

However if we do this multiple times for a complex nested data structure then an update to the top level observ-hash will be offset by n nextTick calls where n is the depth of the nested tree. This is acceptable for node where nextTick works recursively in a blocking fashion but not for browsers that don't have a true nextTick

cc @ljharb @Matt-Esch (suggestions on performance appreciated)

cc @domenic I need a node style nextTick which schedules at the end of the current event loop and blocks when called recursively, does it exist? Does WHATWG plan to create it? Do promises use this as well?

@Raynos
Copy link
Owner Author

Raynos commented Feb 23, 2014

It turns out you can implement nextTick for browsers with the event loop starving functionality.

Browser support is pretty good

cc @domenic where do I suggest to the WHATWG to expose this primitive without abusing MutationObserver ?

@domenic
Copy link

domenic commented Feb 24, 2014

That's a great question, and actually ties into some ongoing work that the HTML spec editor (@Hixie) and others are doing. It's a bit complicated though.

Right now there are various things in the HTML spec that use some specialized, somewhat ad-hoc form of "microtasks." These are: custom elements, table sorting, and mutation observers. Mutation observers are the only one of these that has shipped.

Also, mutation observers use microtasks in a kind of weird fashion: the execution is ordered by observer creation time, not e.g. mutation order. And there's something a bit tricky that I don't recall the details about, regarding the exact ordering of delivery when you mutate something inside a mutation observer. The result is, you can't model it as e.g. FIFO queue, and even something like a priority queue is not compatible with current behavior. It looks like we're going to have to model the "mutation observer checkpoint" as a single microtask that loops through its own list of mutation observer callbacks in its own idiosyncratic order, instead of being able to put each mutation observer callback into its own microtask.

In ES6, promises also use this microtask concept. There are attempts to draft this microtask mechanism (confusingly, under the name "task" instead of microtask) in the current ES6 draft. But I have heard from a few people that the semantics there are insufficient for the browsers' needs, so it is getting revamped. (@allenwb, can you confirm?) Promises' use of microtasks are very simple; all they care about is FIFO ordering with respect to other promises---you could e.g. interleave mutation observer microtasks at any point.

Anyway, the plan is indeed to unify all of these under a single microtask concept. Related bugs: #22185, #22296, @Hixie was trying to do that on Friday (IRC logs start here), with the conclusion it seems that he's admitted defeat on letting mutation observers participate generically and will go with the above-described special-case single-microtask.

NOW THAT YOU KNOW WHERE WE ARE.

I would imagine that, once we get this mess figured out (which largely seems to be @Hixie's job right now, although @allenwb took a stab at it in ES, and they don't seem to be collaborating as much as would be ideal), then people would probably agree that giving direct, no-overhead access to the microtask queue would be possible and ideal. I'd propose window.asap(function () { ... }) for now, maybe with an eye toward global.asap(function () { ... }) in ES7. (Of course we could be boring and do window.queueMicrotask)

In the meantime, I'd use

function asap(fn) {
    Promise.resolve().then(fn);
}

where possible, as it does not have the confusing ordering or DOM-touching that mutation observers force; otherwise you can use mutation observers.

@Raynos
Copy link
Owner Author

Raynos commented Feb 24, 2014

Edit: This is just a chrome specific issue

@domenic your asap is not the same as process.nextTick

// starves event loop
function nextTick(fn) {
  var elem = document.createElement('div')
  new MutationObserver(fn).observe(elem, { attributes: true })
  elem.setAttribute('x', 'y')
}

Try running

asap(function loop() {
  console.log('once')
  asap(loop)
})
setImmediate(function () { console.log('never prints') })

Your asap does not starve the event loop and 'never prints' get's written to the console.

however the DOMMutation observ version of nextTick DOES starve the event loop and 'never prints' never get's written to the console.

For my use-case it's fundamentally important to have a task / microtask / window.asap that starves the event loop. Have multiple nextTick calls be pipelined as one per event loop introduces unacceptable latency, I want all nextTick calls to schedule blockingly / starvingly at the end of my current tick of the event loop.

Your implementation of asap might as well be function asap(fn) { setImmediate(fn) }

@domenic
Copy link

domenic commented Feb 24, 2014

@Raynos by spec, my asap does indeed starve the event loop. Did you read what I wrote? Promises use microtasks. They do not use macrotasks (HTML spec "tasks") like setImmediate does.

@Raynos
Copy link
Owner Author

Raynos commented Feb 24, 2014

Secondly it's really important that asap() gets properly documented as starving the event loop.

Whether task or microtask as per ES6 / Promise spec should starve the event loop is not my concern.

I just want an asap primitive that has the starving behavior for efficiency reasons.

We should also properly spec / document whether MutationObserver should starve the event loop or not and whether Promise#then() should starve the event loop or not.

@Raynos
Copy link
Owner Author

Raynos commented Feb 24, 2014

@domenic run the code in the chrome console. I guess it's a bug in chrome then.

@domenic
Copy link

domenic commented Feb 24, 2014

It's a bit disappointing that I wrote all that and you seem to have not understood it at all :(

@Raynos
Copy link
Owner Author

Raynos commented Feb 24, 2014

I understand it. But I dont see where it is defined that microtasks strave the event loop

@domenic
Copy link

domenic commented Feb 24, 2014

Oh. I guess I didn't make that clear. But it's in both the HTML and ES spec versions. You keep running microtasks until the queue is empty. (Ergo, if it's never empty, you never stop running them, and the event loop never turns, i.e. is starved.)

That is what distinguishes them from macrotasks (HTML "tasks").

@Raynos
Copy link
Owner Author

Raynos commented Feb 24, 2014

So I read the ES6 spec some more and it seems you are right, it has the recursive event loop starving semantics.

However it does seem that chrome's promise implementation is a bit weird so that threw me off.

@Raynos
Copy link
Owner Author

Raynos commented Feb 24, 2014

I re-read the linked HTML spec section and it's pretty clear about the starving semantics of the microtasks as well (although it has gotos in the steps and is hard to follow >_<)

@allenwb
Copy link

allenwb commented Feb 24, 2014

see http://esdiscuss.org/topic/es6-tasks-and-taskqueues for comments relating to this thread

cscott added a commit to cscott/es6-shim that referenced this issue Mar 4, 2014
…able.

From Raynos/observ-struct#2 (comment)
this is an efficient implementation of `EnqueueTask` on platforms which
implement `Promise` natively (even if we end up overriding the native
`Promise` to work around bugs).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants