admin管理员组文章数量:1332184
Consider the following code written in ES6:
function waitForMessage() {
return new Promise((resolve, reject) => {
function handler(event) {
resolve(event);
window.removeEventListener('message', handler);
};
window.addEventListener('message', handler);
});
}
function loop(event) {
// do something (synchronous) with event
waitForMessage().then(loop);
}
waitForMessage().then(loop);
In this piece of code, waitForMessage
installs an event handler waiting for a message to arrive at the current window. Once it has arrived, the promise returned by waitForMessage
is being resolved and the event handler removed.
In loop
, a new promise is being generated by waitForMessage
as soon as the job enqueued by resolving the previous promise is being run.
Now my question is whether loop
may not get all messages posted at window due to a timing problem: If the jobs enqueued by Promise.prototype.resolve
are not always being run before any tasks enqueued in the browser's event loop, it may be the case that an message
event is begin dispatched at window
while there is currently no handler listening for this event.
What does the standard say about the timing of these different types of jobs/tasks, namely resolving the callbacks of promises and the dispatching of events from outside of the ES6 world?
(I just took the message
event as an example, I am equally interested in other events, like click
or popstate
events.)
P.S.: As this has been asked for several times in the ments below, let me describe what I was hoping for with the above code:
I would like to use ES6 features to avoid having to deal too much with callbacks in my code and to make sure that added event listeners are removed in time to avoid memory leaks. Thus I have written something along these lines:
const looper = (element, type, generator) => (... args) => new Promise((resolve, reject) => {
const iterator = generator(...args);
const listener = (event) => {
try {
let {done, value} = iterator.next(event);
} catch (error) {
reject(error);
element.removeEventListener(type, listener);
}
if (done) {
resolve(value);
element.removeEventListener(type, listener);
}
}
element.addEventListener(type, listener);
listener();
});
const loop = (element, type, generator) => looper(element, type, generator)();
With this code, I can do something like:
loop(window, 'message', function *() {
event = yield;
// do something synchronous with event
if (shouldStopHere) {
return result;
}
});
This code does not suffer from the issues my question is about; only one promise is created and the event handler is only attached and removed once. Removal of the event handler is guaranteed when the inner function returns.
It is well known that generators in ES6 can also be used for handling promises (like the asyncio
package does in Python 3.4). There is proposal for ES7 to include some sugar for these async functions, namely . I was hoping to use this sugar (which is supported by Traceur at the moment) to sugarize my above loop
function. However, the proposed async functions only deal with promises so I tried to rewrite my loop code in a fashion that produces a result of promises, the result of which I posted at the beginning of my question.
Consider the following code written in ES6:
function waitForMessage() {
return new Promise((resolve, reject) => {
function handler(event) {
resolve(event);
window.removeEventListener('message', handler);
};
window.addEventListener('message', handler);
});
}
function loop(event) {
// do something (synchronous) with event
waitForMessage().then(loop);
}
waitForMessage().then(loop);
In this piece of code, waitForMessage
installs an event handler waiting for a message to arrive at the current window. Once it has arrived, the promise returned by waitForMessage
is being resolved and the event handler removed.
In loop
, a new promise is being generated by waitForMessage
as soon as the job enqueued by resolving the previous promise is being run.
Now my question is whether loop
may not get all messages posted at window due to a timing problem: If the jobs enqueued by Promise.prototype.resolve
are not always being run before any tasks enqueued in the browser's event loop, it may be the case that an message
event is begin dispatched at window
while there is currently no handler listening for this event.
What does the standard say about the timing of these different types of jobs/tasks, namely resolving the callbacks of promises and the dispatching of events from outside of the ES6 world?
(I just took the message
event as an example, I am equally interested in other events, like click
or popstate
events.)
P.S.: As this has been asked for several times in the ments below, let me describe what I was hoping for with the above code:
I would like to use ES6 features to avoid having to deal too much with callbacks in my code and to make sure that added event listeners are removed in time to avoid memory leaks. Thus I have written something along these lines:
const looper = (element, type, generator) => (... args) => new Promise((resolve, reject) => {
const iterator = generator(...args);
const listener = (event) => {
try {
let {done, value} = iterator.next(event);
} catch (error) {
reject(error);
element.removeEventListener(type, listener);
}
if (done) {
resolve(value);
element.removeEventListener(type, listener);
}
}
element.addEventListener(type, listener);
listener();
});
const loop = (element, type, generator) => looper(element, type, generator)();
With this code, I can do something like:
loop(window, 'message', function *() {
event = yield;
// do something synchronous with event
if (shouldStopHere) {
return result;
}
});
This code does not suffer from the issues my question is about; only one promise is created and the event handler is only attached and removed once. Removal of the event handler is guaranteed when the inner function returns.
It is well known that generators in ES6 can also be used for handling promises (like the asyncio
package does in Python 3.4). There is proposal for ES7 to include some sugar for these async functions, namely https://github./lukehoban/ecmascript-asyncawait. I was hoping to use this sugar (which is supported by Traceur at the moment) to sugarize my above loop
function. However, the proposed async functions only deal with promises so I tried to rewrite my loop code in a fashion that produces a result of promises, the result of which I posted at the beginning of my question.
- 2 You're kind of bastardizing promises on this one. Promises should not really be used for events that can happen more than once. – Madara's Ghost Commented Jan 26, 2015 at 7:56
- 1 I fail to see the point why promises should not be used in this case. In any case, the question remains. – Marc Commented Jan 26, 2015 at 8:04
- 1 Well, because a Promise is an object that represents some data that will be available in the future, like the result of an asynchronous function call. It's not suited for something that may or may not ever arrive, or something that would arrive more than once. – Madara's Ghost Commented Jan 26, 2015 at 8:11
- 3 @Marc what advantage does it offer over putting it in an event handler without involving a promise? – Benjamin Gruenbaum Commented Jan 26, 2015 at 8:28
-
1
Whatever other issues there are with this approach, I think you need to say
function loop(event) { return waitForMessage().then(loop); }
. Note thereturn
. – user663031 Commented Jan 26, 2015 at 9:03
3 Answers
Reset to default 4Addressing your specific issue
The behavior of the promise constructor is well defined both in ES6 promises and promise implementations that implement the constructor spec (virtually everything except old jQuery):
var p = new Promise(function(resolve, reject){
// ALWAYS runs synchronously
console.log("Hello");
});
console.log("World"); // program always logs "Hello World", events will never be missed
This is well specified and by design. The use case you are describing is mostly why this behavior was guaranteed to work in the spec.
Note that while the promise constructor is specified to run synchronously you still have a race condition with the then
- http://jsfiddle/vko4p6zz/
I don't think promises are the correct abstraction here (see jfriend00's answer) but it might make sense with more context - you can rely on the execution order of the promise constructor. You can see this in the specification - new Promise
then calls InitializePromise
which in turn synchronously calls the function passed.
A possibly better approach.
Just like a promise represents a single value + time, there is an abstraction called an observable that represents multiple values + time. Just like a promise is a functional callback an observable is a functional event emitter. Here is an example using one library (RxJS) - there are several other libraries implementing the concept:
var messageStream = Rx.Observable.fromEvent(window, 'message');
messageStream.subscribe(function(value){
console.log(value); // unwrapping the event
});
In addition to unwrapping with subscribe - you can also map
events, filter them, flatMap
them and more - they pose just like promises and are as close as I think you can/should get to promises in this context.
At best, your current approach would be relying on a precise and consistent implementation of promise .then()
handlers such they never allowed other queued events to process before they were called.
At worst, you would definitely have a chance at missing events.
If you look at Benjamin's jsFiddle and run it in both Chrome and Firefox, you will see that Firefox misses an event (I don't see a missing event in Chrome).
What is clear is that your current design is simply not a safe design because it relies on implementation details (that may or may not be well specified and may or may not be implemented perfectly even if specified) that your code simply does not need to depend on. Whether or not some spec says that this might or should work, this is a brittle design that does not need to be susceptible to this issue.
What would make more sense is to base your design on a constantly installed event listener so there is no way you can ever miss an event. It is probably still possible to use promises with that type of design, but as others have pointed out that is rarely (if ever) a preferred design pattern because promises are not designed for repeated events so you have to continue to create new promises after each event and you will generally find that just using a classic callback for an event handler is a much cleaner way of doing things and takes none of the risks that your current approach is taking.
For example, your proposed code could simply be replaced with this:
window.addEventListener('message', function(e) {
// the synchronous code you mentioned to process the event
});
which is simpler and is guaranteed to not have any vulnerability to missing messages that your code might have. This code is also more consistent with general design patterns for event driven code in mon use for a wide variety of events (such as click events that you mentioned).
What does the standard say about the timing of these different types of jobs/tasks, namely resolving the callbacks of promises and the dispatching of events from outside of the ES6 world?
- Promises are run in the micro task queue.
- UI Events are run in the macro task queue.
The HTML5 spec mandates that the micro task queue is fully drained before the macro task queue starts its next task.
The DOM spec is currently undergoing changes because they want to refine how observers interleave with promises, but they will remain on the micro task queue.
本文标签: javascriptTiming of resolving of promises and handling browser eventsStack Overflow
版权声明:本文标题:javascript - Timing of resolving of promises and handling browser events - Stack Overflow 内容由网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://www.betaflare.com/web/1742247617a2440148.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论