How to convert Node.js async streaming callback into an async generator?

前端 未结 3 516
猫巷女王i
猫巷女王i 2021-01-14 11:10

I have a function that streams data in batches via a callback.

Each batch will await the callback function before fetching another batch and the entire function retu

相关标签:
3条回答
  • 2021-01-14 11:37

    Would it work if there will be typescript solution?

    It should handle condition when callback is called faster then promise is resolved a couple of times. Callback can be a method that has this signature callback(error, result, index) It is set to finish when callback is called with no arguments. Usage:

    asAsyncOf(this.storage, this.storage.each);
    

    Solution:

    function asAsyncOf<T1, T2, T3, T4, Y>(c, fn: { (a: T1, a1: T2, a2: T3, a3: T4, cb: { (err?, res?: Y, index?: number): boolean }): void }, a: T1, a1: T2, a2: T3, a3: T4): AsyncGenerator<Y>
    function asAsyncOf<T1, T2, T3, Y>(c, fn: { (a: T1, a1: T2, a2: T3, cb: { (err?, res?: Y, index?: number): boolean }): void }, a: T1, a1: T2, a3: T3): AsyncGenerator<Y>
    function asAsyncOf<T1, T2, Y>(c, fn: { (a: T1, a1: T2, cb: {(err?, res?: Y, index?: number): boolean}): void}, a: T1, a1: T2): AsyncGenerator<Y>
    function asAsyncOf<T, Y>(c, fn: { (a: T, cb: { (err?, res?: Y, index?: number): boolean }): void }, a: T): AsyncGenerator<Y>
    function asAsyncOf<Y>(c, fn: { (cb: {(err?, res?: Y, index?: number): boolean}): void}): AsyncGenerator<Y>
    async function* asAsyncOf(context, fn, ...args) {
        let next = (result?) => { };
        let fail = (err) => { };
        let finish = {};
        const items = [];
        let started = true;
        try {
            fn.apply(context, [...args, function (err, result, index) {
                const nextArgs = [].slice.call(arguments, 0);
                if (nextArgs.length === 0) {
                    started = false;
                    next(finish);
                    return true;
                }
                if (err) {
                    fail(err);
                    return true;
                }
                items.push(result);
                next(result);
            }]);
        } catch (ex) {
            fail(ex);
        }
        while (true) {
            const promise = started ? new Promise((resolve, error) => {
                next = resolve;
                fail = error;
            }) : Promise.resolve(finish);
            const record = await promise;
            if (record === finish) {
                while (items.length) {
                    const item = items.shift();
                    yield item;
                }
                return;
            }
            while (items.length) {
                const item = items.shift();
                yield item;
            }
        }
    }
    export { asAsyncOf };
    
    0 讨论(0)
  • 2021-01-14 11:41

    No, I don't think there's a way to implement this transformation in a way that's easy to understand and easy to follow. However, I would recommend to drop the deferreds (you're never rejecting anyway) and just use the promise constructor. Also I'd rather implement an asynchronous generator right away.

    function queue() {
        let resolve = () => {};
        const q = {
            put() {
                resolve();
                q.promise = new Promise(r => { resolve = r; });
            },
            promise: null,
        }
        q.put(); // generate first promise
        return q;
    }
    function toAsyncIterator(callbackStream) {
        const query = queue();
        const result = queue();
        const end = callbackStream(batch => {
            result.put(batch);
            return query.promise;
        }).then(value => ({value, done: true}));
        end.catch(e => void e); // prevent unhandled promise rejection warnings
        return {
            [Symbol.asyncIterator]() { return this; },
            next(x) {
                query.put(x);
                return Promise.race([
                    end,
                    result.promise.then(value => ({value, done:false})
                ]);
            }
        }
    }
    async function* batchToAsyncIterator(batchCallbackStream) {
        for await (const batch of toAsyncIterator(batchCallbackStream)) {
            // for (const val of batch) yield val;
            // or simpler:
            yield* batch;
        }
    }
    
    0 讨论(0)
  • 2021-01-14 11:56

    You need a event bucket, here is an example:

    function bucket() {
      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 = new bucket();
    
      setInterval(() => {
        evts.push(Date.now());
        evts.push(Date.now() + '++');
      }, 1000);
    
      for await (let evt of evts) {
        console.log(evt);
      }
    })();

    0 讨论(0)
提交回复
热议问题