4 ms·
This. Ran into this very issue when I was implementing promises in Mithril ( http://lhorie.github.io/mithril http://lhorie.github.io/mithril ). This really make
by lhorie 13y ago
This. Ran into this very issue when I was implementing promises in Mithril ( http://lhorie.github.io/mithril http://lhorie.github.io/mithril ). This really makes promise performance a much uglier beast than it needs to be.
I took a less conventional road there: I deliberately violate the A+ spec and let surprises like the ones in the article example happen, in order to get good performance without hacks/bloat. Typically, promise resolution is usually done asynchronously anyways (otherwise, why bother using promises?), so this caveat would only be a problem in some serious case of misuse.
In any case, surprises are fixable in application space (i.e. just move lines of code around), but performance degradation due to some deep implementation detail in the promise API / browsers not playing ball is nearly impossible to fix unless you switch to a non-conformant promise library (in which case, you'd have to fix the surprises anyways - assuming you had any cases of misuse to begin with).
- spion 13y agoA+ doesn't require that you use the browser's asynchronous scheduling mechanism, it only requires that the promise callback is always executed after the current function. There is a really neat hack to achieve this without completely relying on the browser's scheduler by implementing your own callback queue. asap(callback); which will push to an array of callbacks that you need to execute and then call a loop if one isn't currently active: function asap(cb) { callbacks.push(cb); setTimeout(flushQueue, 5); } Flushqueue will run all the callbacks from the queue in a loop, then empty it function flushqueue() { if (flushing) return; flushing = true; for (var k = 0; k < callbacks.length; ++k) { callbacks[k](); } flushing = false; } Now if some of the callbacks schedules a new function to execute using asap promise.then(function first(x) { return alreadyResolvedPromise.then(function second(y) { return y.some.property; }); }); then when "promise" resolves, first()'s execution will push a new function to the queue, changing queue.length, causing that loop above to cause one more iteration - but only after first is complete. (If you don't nest the callbacks, then the push will happen before flushqueue even starts executing) The code above is overly simplified but I think its enough to demonstrate the idea. (Huge thanks Stefan Penner for explaining it to me :D)
- exogen 13y agoUnfortunately there's no way to remove the need for flushQueue being called with the browser's asynchronous scheduling mechanism (using setTimeout in your example) in the case where it isn't already flushing – that's the issue. Flushing the callback queue to completion is an optimization, not really a fix.
- spion 13y agoYes, truly asynchronous things which took less than 5ms will now take at least 5ms to execute. But its a stretch to say that for Promises/A+ implementations "... always require at least one more iteration of the event loop to resolve. This is not necessarily true of the standard callback approach." as the article states. A fully synchronous chain of 20 operations wont take 100ms - it will only take 5ms
- exogen 13y agoI'm not really disputing the time anything will take, rather that "A+ doesn't require that you use the browser's asynchronous scheduling mechanism" – it very much does, even though that scheduling mechanism might be to occasionally flush your own managed event queue instead of handing every callback to the browser.
- lhorie 13y ago>> it will only take 5ms This is true in the scenario of a linear chain, e.g. `.then(foo).then(bar)`, but the spec also requires that the following should work (for interop purposes, among a few other scenarios): .then(function() {return someOtherpromise.then(doSomething)}) So you might incur other harder-to-track-down penalties if you're doing interop between libraries somewhere, or if the promise chain has dependencies on the data provided by upstream resolvers.
- spion 13y agoIf someOtherpromise is an already resolved promise from your own library, then it will still take just 5ms - the neat hack here is that asap will simply push another callback at the end of the queue (which is still being processed by the for loop) and the loop will simply get one more iteration straight in the middle of its execution. If its from another library, then that other library may be using its own scheduler and you will loose those 5ms either way...