tc39/proposal-async-iteration

how do you handle sockets going dead or timing out?

kaizhu256 opened this issue · 5 comments

can you include timeout example-code in README.md? otherwise, this is unusable for reading from network sockets.

for example, this is how timeouts would be handled (with proper cleanup of nodejs stream) using callbacks:

function consumeReadableStream(stream, consumeChunk, callback) {
/*
 * stream - readable stream
 * consumeChunk - has signature - function (chunk) {...}
 * callback - has signature - function (error) {...}
 */
    var callbackOnce, done, timerTimeout;

    callbackOnce = function (error) {
    /*
     * this function will ensure callback is called only once
     */
        if (done) {
            return;
        }
        done = true;
        // clear timeout
        clearTimeout(timerTimeout);
        callback(error);
    };

    // init timeout handler
    timerTimeout = setTimeout(function () {
        callbackOnce(new Error('30000 ms timeout'));
        stream.destroy();
    }, 30000);

    stream.on('data', consumeChunk);
    stream.on('end', callbackOnce);
    stream.on('error', callbackOnce);
};

You need to make your own Async Iterator, I wrote a library for converting event based interfaces into Async Iterables with correct closing abilities like you describe. I need to write more documentation and examples but basically your example would be:

import Stream from "@jx/stream"

function dataStream(nodeStream) {
    return new Stream(stream => {
        // Cause the stream to return early if time out
        const timer = setTimeout(_ => stream.throw(new Error("Timed out!")), 10000)

        nodeStream.on('data', stream.yield)
        nodeStream.on('error', stream.throw)
        nodeStream.on('end', stream.return)
        
       // The return value is the cleanup action
       // it's guaranteed to be called once regardless of
       // method of exit
       return _ => {
           clearTimeout(timer)
           stream.destroy()
       }
    })
}

async function main() {
    for await (const chunk of dataStream(someNodeStream)) {
        console.log(chunk)
    }
}

main()

i see. how would end-user catch the timeout? is it something like following:

// using callback
consumeReadableStream(someNodeStream, function (chunk) {
    console.log(chunk);
// handle timeout
}, function (error) {
    console.error(error);
});

// using async-iterator
async function main() {
    try {
        for await (const chunk of dataStream(someNodeStream)) {
            console.log(chunk)
        }
    // handle timeout
    } catch (errorCaught) {
        console.error(errorCaught);
    }
}

Yes, it's essentially no different to synchronous iterators, the .next method of either type can either return an IteratorResult object, or it can throw an error. So try-catch works just at it would if you were consuming a synchronous iterator via a for-of loop.

There's nothing particularly special about my library in this regard, my library is essentially just a way of turning something event-based into just an async iterator (the names stream.yield/stream.throw/stream.return correspond exactly to the keywords within an async generator by the same name), and just like like synchronous iterators consuming them can work in pretty much the same way.

If you have questions regarding my library specifically feel free to ask on m y repository as I don't think we really need to be discussing userland libraries on the proposal repository.

Closing as this is a question suited for StackOverflow, not a bug report for the spec.