admin管理员组

文章数量:1353627

I am trying to build a way to create a generator which can yield DOM events. More generally, I want to create a way to convert an event system to an async system yielding events.

My initial code example works, but I can see an issue with lifting the resolve function from the Promise so that I can call that function once the event es in.

class EventPropagation {
  constructor(id) {
    const button = document.getElementById(id);
    let _resolve;

    button.addEventListener("click", event => {
      if (_resolve) {
       _resolve(event);
      }
    });

    let _listen = () => {
      return new Promise(resolve => {
       _resolve = resolve;
      });
    }

    this.subscribe = async function*() {
      const result = await _listen();
      yield result;
      yield * this.subscribe();
    }
  }
}

async function example() {
  const eventPropagation = new EventPropagation("btn");
  for await (const event of eventPropagation.subscribe()) {
    console.log(event);
  }
}

// call the example function
example();

My question is: Is there a better way of building something like this? There are a lot of things to think about, like multiple events ing in at the same time or cleaning up the listener and the subscriptions. My goal is not to end up with a reactive library but I do want to create small transparent functions which yield events asynchronously.

fiddle

Edited 14 dec 2017 (Edited in response to Bergi's ment)

Async Generators

Babel and a few plugins later; async generators aren't a problem:

const throttle = ms => new Promise(resolve => setTimeout(resolve, ms));

const getData = async() => {
    const randomValue = Math.floor(Math.random() * 5000 + 1);
    await throttle(randomValue);
    return `The random value was: ${randomValue}`;
}

async function* asyncRandomMessage() {
    const message = await getData();
    yield message;

    // recursive call
    yield *asyncRandomMessage();
}

async function example() {
    for await (const message of asyncRandomMessage()) {
        console.log(message);
    }
}

// call it at your own risk, it does not stop
// example();

What I want to know is how I transform a series of individual callback calls into an async stream. I can't imagine this problem isn't tackled. When I look at the library Bergi showed in the ments I see the same implementation as I did, namely: "Store the resolve and reject functions somewhere the event handler can call them." I can't imagine that would be a correct way of solving this problem.

I am trying to build a way to create a generator which can yield DOM events. More generally, I want to create a way to convert an event system to an async system yielding events.

My initial code example works, but I can see an issue with lifting the resolve function from the Promise so that I can call that function once the event es in.

class EventPropagation {
  constructor(id) {
    const button = document.getElementById(id);
    let _resolve;

    button.addEventListener("click", event => {
      if (_resolve) {
       _resolve(event);
      }
    });

    let _listen = () => {
      return new Promise(resolve => {
       _resolve = resolve;
      });
    }

    this.subscribe = async function*() {
      const result = await _listen();
      yield result;
      yield * this.subscribe();
    }
  }
}

async function example() {
  const eventPropagation = new EventPropagation("btn");
  for await (const event of eventPropagation.subscribe()) {
    console.log(event);
  }
}

// call the example function
example();

My question is: Is there a better way of building something like this? There are a lot of things to think about, like multiple events ing in at the same time or cleaning up the listener and the subscriptions. My goal is not to end up with a reactive library but I do want to create small transparent functions which yield events asynchronously.

fiddle

Edited 14 dec 2017 (Edited in response to Bergi's ment)

Async Generators

Babel and a few plugins later; async generators aren't a problem:

const throttle = ms => new Promise(resolve => setTimeout(resolve, ms));

const getData = async() => {
    const randomValue = Math.floor(Math.random() * 5000 + 1);
    await throttle(randomValue);
    return `The random value was: ${randomValue}`;
}

async function* asyncRandomMessage() {
    const message = await getData();
    yield message;

    // recursive call
    yield *asyncRandomMessage();
}

async function example() {
    for await (const message of asyncRandomMessage()) {
        console.log(message);
    }
}

// call it at your own risk, it does not stop
// example();

What I want to know is how I transform a series of individual callback calls into an async stream. I can't imagine this problem isn't tackled. When I look at the library Bergi showed in the ments I see the same implementation as I did, namely: "Store the resolve and reject functions somewhere the event handler can call them." I can't imagine that would be a correct way of solving this problem.

Share Improve this question edited Jun 20, 2020 at 9:12 CommunityBot 11 silver badge asked Dec 13, 2017 at 11:07 Mr. BaudinMr. Baudin 2,2542 gold badges18 silver badges25 bronze badges 3
  • 3 It looks like you are trying to implement an async generator. These don't exist yet, but if you look at the proposal you will also find this issue or even a library doing all the work for you. Also notice that Observables might be a better fit for representing DOM events. – Bergi Commented Dec 13, 2017 at 12:11
  • Thank you for the ment and the links. I want to use this pattern for every type of async stream: DOM events, Firebase, WebWorkers etc etc. I am looking for an easy to implement solution for all of these async sequences. If I can't build it like this I will def. use an Observer pattern. – Mr. Baudin Commented Dec 13, 2017 at 12:17
  • Have a look at this answer – Bergi Commented Mar 10, 2019 at 22:36
Add a ment  | 

3 Answers 3

Reset to default 10

You need a event bucket, here is an example:

function evtBucket() {
  const stack = [],
                iterate = bucket();

  var next;

  async function * bucket() {
        while (true) {
            yield new Promise((res) => {
                if (stack.length > 0) {
                    return res(stack.shift());
                }

                next = res;
            });
        }
  }  

  iterate.push = (itm) => {
    if (next) {
      next(itm);
      next = false;
      return;
    }

    stack.push(itm);
  }  

  return iterate;
}

;(async function() {
  let evts = evtBucket();

  setInterval(()=>{
    evts.push(Date.now());
    evts.push(Date.now() + '++');
  }, 1000);

  for await (let evt of evts) {
    console.log(evt);
  }
})();

My best solution thus far has been to have an internal EventTarget that dispatches events when new events are added onto a queue array. This is what I've been working on for a JS modules library (including used modules here). I don't like it... But it works.

Note: This also handles the new AbortSignal option for event listeners in multiple places.

export function isAborted(signal) {
    if (signal instanceof AbortController) {
        return signal.signal.aborted;
    } else if (signal instanceof AbortSignal) {
        return signal.aborted;
    } else {
        return false;
    }
}

export async function when(target, event, { signal } = {}) {
  await new Promise(resolve => {
    target.addEventListener(event, resolve, { once: true, signal });
  });
}

export async function *yieldEvents(what, event, { capture, passive, signal } = {}) {
    const queue = [];
    const target = new EventTarget();

    what.addEventListener(event, event => {
        queue.push(event);
        target.dispatchEvent(new Event('enqueued'));
    }, { capture, passive, signal });

    while (! isAborted(signal)) {
        if (queue.length === 0) {
            await when(target, 'enqueued', { signal }).catch(e => {});
        }

        /**
         * May have aborted between beginning of loop and now
         */
        if (isAborted(signal)) {
            break;
        } else {
            yield queue.shift();
        }
    }
}

The example provided by NSD, but now in Typescript


class AsyncQueue<T> {
  private queue: T[] = [];
  private maxQueueLength = Infinity;
  private nextResolve = (value: T) => {};
  private hasNext = false;

  constructor(maxQueueLength?: number) {
    if (maxQueueLength) {
      this.maxQueueLength = maxQueueLength;
    }
  }

  async *[Symbol.asyncIterator]() {
    while (true) {
      yield new Promise((resolve) => {
        if (this.queue.length > 0) {
          return resolve(this.queue.shift());
        }
        this.nextResolve = resolve;
        this.hasNext = true;
      });
    }
  }

  push(item: T) {
    if (this.hasNext) {
      this.nextResolve(item);
      this.hasNext = false;
      return;
    }
    if (this.queue.length > this.maxQueueLength) {
      this.queue.shift();
    }
    this.queue.push(item);
  }
}

(async function () {
  const queueu = new AsyncQueue<string>();

  setInterval(() => {
    queueu.push(Date.now().toString());
    queueu.push(Date.now().toString() + "++");
  }, 1000);

  for await (const evt of queueu) {
    console.log(evt);
  }
})();

本文标签: async awaitHow to build an event generator in JavaScriptStack Overflow