解决承诺和处理浏览器事件的时间 [英] Timing of resolving of promises and handling browser events

查看:111
本文介绍了解决承诺和处理浏览器事件的时间的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

考虑以下ESD中编写的代码:

  function waitForMessage(){
return new Promise ,reject)=> {
函数处理程序(事件){
resolve(event);
window.removeEventListener('message',handler);
};
window.addEventListener('message',handler);
});
}

函数循环(event){
//做某事(同步)事件
waitForMessage()然后(循环);
}
waitForMessage()。then(loop);

在这段代码中, waitForMessage installs等待消息到达当前窗口的事件处理程序。一旦到达, waitForMessage 返回的承诺正在解决,并移除事件处理程序。



loop ,一旦通过解决之前的承诺正在运行,排入队伍的工作就会由 waitForMessage 生成一个新的承诺。 / p>

现在我的问题是,由于时间问题,是否循环可能无法收到窗口发布的所有消息:如果作业在$ code中排队的Promise.prototype.resolve 并不总是在浏览器的事件循环中排队的任何任务之前运行,可能是消息事件开始发送在窗口,而目前没有处理程序侦听此事件。



标准说明了这些不同类型工作/任务的时间安排,即解决承诺的回调以及从ES6世界之外派遣事件的标准?



(我刚刚收到消息 ev作为一个例子,我对其他事件同样感兴趣,例如点击 popstate 事件。)



PS:由于在下面的评论中已经被要求了好几次,请让我用以上代码描述我希望的内容:



我想使用ES6功能,以免在我的代码中处理太多的回调,并确保添加的事件侦听器及时删除,以避免内存泄漏。因此,我已经写了一些这样的东西:

  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(元素,类型,生成器)();

使用此代码,我可以执行以下操作:

  loop(window,'message',function *(){
event = yield;
//做某事与事件
同步if (shouldStopHere){
return result;
}
});

此代码不会遇到我的问题所在的问题;只创建一个承诺,事件处理程序只被附加和删除一次。内部函数返回时保证事件处理程序的移除。



众所周知,ES6中的生成器也可以用于处理承诺(如 asyncio 软件包在Python 3.4)。有人提议ES7为这些异步功能添加一些糖,即 https://github.com/lukehoban/ ECMAScript的asyncawait 。我希望使用这种糖(此时由Traceur支持)将我上述的循环功能糖化。但是,所提出的异步函数只处理承诺,所以我试图以一种产生承诺结果的方式重写我的循环代码,其结果是在我的问题开始时发布。

解决方案

解决您的具体问题



承诺构造函数的行为在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.

这篇关于解决承诺和处理浏览器事件的时间的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆