且构网

分享程序员开发的那些事...
且构网 - 分享程序员编程开发的那些事

解决承诺和处理浏览器事件的时间

更新时间:2022-11-18 11:27:03

解决您的具体问题



承诺构造函数的行为在ES6承诺和承诺中都有很好的定义实现构造函数规范(几乎除旧jQuery之外的所有内容):

  var p = new Promise(function(resolve,reject) {
// ALWAYS同步运行
console.log(Hello);
});
console.log(World); //程序总是记录Hello World,事件永远不会错过

这是很好的指定和设计。您正在描述的用例主要是为什么这种行为被保证在规范中工作。



请注意,虽然promise构造函数被指定为同步运行,具有然后 的竞争条件 - http:/ /jsfiddle.net/vko4p6zz/



我不认为承诺是正确的抽象在这里(参见jfriend00的答案),但它可能有更多的上下文 - 你可以依赖promise构造函数的执行顺序。您可以在规范中看到 - new Promise 然后调用 InitializePromise ,然后同步调用传递的函数。



一个可能更好的方法。



就像一个承诺代表一个单一的值+时间,有一个叫做observable的抽象代表多个值+时间。就像一个承诺是一个功能回调,可观察是一个功能事件发射器。以下是使用一个库(RxJS)的示例 - 还有其他几个实现概念的库:

  var messageStream = Rx.Observable .fromEvent(window,'message'); 
messageStream.subscribe(function(value){
console.log(value); //展开事件
});

除了使用订阅解开外,您还可以 map 事件,过滤他们, flatMap 他们和更多 - 他们组成就像承诺,并且是尽可能接近我认为你可以/应该在这个上下文中承诺。 / p>

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 comments 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.com/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.

Addressing 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.net/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 compose just like promises and are as close as I think you can/should get to promises in this context.