tc39 / proposal-async-iteration

Asynchronous iteration for JavaScript
https://tc39.github.io/proposal-async-iteration/
MIT License
859 stars 44 forks source link

how do you handle sockets going dead or timing out? #115

Closed kaizhu256 closed 7 years ago

kaizhu256 commented 7 years ago

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

kaizhu256 commented 7 years ago

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);
};
Jamesernator commented 7 years ago

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()
kaizhu256 commented 7 years ago

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);
    }
}
Jamesernator commented 7 years ago

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.

domenic commented 7 years ago

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