1

I am using Axios to call an api endpoint which has a progress indicator. I would like to convert the onUploadProgress to a generator.

Is there a way to convert this code

setProgress({ state: 'syncing', progress: 0 });

await axios.post(uri.serialize(parsedURI), body2, {
    headers: { session_id: sessionId },
    onUploadProgress: (progress) => {
        setProgress({ state: 'syncing', progress: progress.loaded / progress.total });
    },
});
setProgress({ state: 'syncing', progress: 1 });

Into something like this

yield { state: 'syncing', progress: 0 };

await axios.post(uri.serialize(parsedURI), body2, {
    headers: { session_id: sessionId },
    onUploadProgress: (progress) => {
        yield { state: 'syncing', progress: progress.loaded / progress.total };
    },
});
yield { state: 'syncing', progress: 1 };

the problem is in the yield inside the onUploadProgress, I was thinking there could be a way to use it like when you want to convert a callback into a promise you use

new Promise(resolve => fn(resolve));

maybe there is some valid way of doing it for generators like

new Generator(next => fn(next));
Leonardo da Silva
  • 1,285
  • 2
  • 10
  • 25
  • How would you imagine this "generator" to be consumed? Without that, your question is quite unclear. – Bergi Oct 27 '19 at 18:34
  • based on the answers so far - it seems generators are not really the right tool for the job - stick with a callback, or maybe a *pubsub pattern* – Bravo Oct 27 '19 at 20:30
  • @Bravo An *async* generator might do the job fine, but the OP hasn't stated yet that they are what he meant. – Bergi Oct 28 '19 at 21:05
  • @Bergi - I don't see how an async generator would help when trying to yield values from a callback that gets called multiple times - even if you could, the code would be messy and cumbersome to maintain – Bravo Oct 28 '19 at 21:53
  • @Bravo It's quite doable, although you're right that one would need a [queue](https://stackoverflow.com/a/47157577/1048572) to handle every single callback call argument when those happen faster than the async iteration. – Bergi Oct 28 '19 at 22:05
  • @Bergi - it seems like a ton of work for absolutely zero gain (for this purpose) – Bravo Oct 28 '19 at 22:12
  • @Bravo Depends on the gain the OP is hoping for - he still hasn't stated how he'd like to use this. And if it was fine to skip progress values if the callback is faster than the iteration, the implementation of the generator would be much simplified. But yes, I agree, it might well be the case that the callback or an event emitter is the better solution. – Bergi Oct 28 '19 at 22:16

1 Answers1

2

You can update a local variable in the progress handler and poll it in a loop until the request is complete. Here's a sketch:

let delay = n => new Promise(res => setTimeout(res, n));


async function request(n, onProgress) {
    for (let i = 0; i < n; i++) {
        onProgress(i);
        await delay(200);
    }
    return 'response'
}


async function* requestWithProgress(result) {
    let prog = 0,
        prevProg = 0,
        res = null;

    let req = request(10, n => prog = n)
        .then(r => res = r);

    while (!res) {
        await delay(1);
        if (prog > prevProg)
            yield prevProg = prog;
    }

    result.res = res;
}


async function main() {
    let result = {}
    for await (let prog of requestWithProgress(result))
        console.log(prog)
    console.log(result)
}


main()

Here's another option, without polling, and better return API:

function progressiveAsync(factory) {
    let
        resultPromise = null,
        resultResolver = null,
        genPromise = null,
        genResolver = null,
        stop = {};

    resultPromise = new Promise(r => resultResolver = r);
    genPromise = new Promise(r => genResolver = r);

    async function* gen() {
        while (true) {
            let r = await genPromise;
            if (r === stop) {
                break;
            }
            yield r;
        }
    }

    factory(
        val => {
            genResolver(val);
            genPromise = new Promise(r => genResolver = r);
        },
        val => {
            genResolver(stop);
            resultResolver(val);
        }
    );

    return [gen(), resultPromise];
}

//


async function testRequest(opts /* count, onProgress */) {
    return new Promise(async res => {
        for (let i = 0; i < opts.count; i++) {
            opts.onProgress(i);
            await new Promise(resolve => setTimeout(resolve, 300));
        }
        res('response!')
    })
}


async function main() {
    let [progress, result] = progressiveAsync((next, done) =>
        testRequest({
            count: 10,
            onProgress: next
        }).then(done));

    for await (let n of progress)
        console.log('progress', n)

    console.log(await result)
}

main()
georg
  • 211,518
  • 52
  • 313
  • 390
  • in reality, you've just moved the progress callback :D – Bravo Oct 27 '19 at 11:18
  • 1
    No, don't do polling. – Bergi Oct 27 '19 at 18:37
  • Thanks for the update, that looks much better :-) I'd also a) put the `factory()` call (and basically the rest of the code) inside the generator function b) put the `genPromise = …` inside the loop to avoid the duplication c) drop the `resultPromise` entirely and instead `return` the result from the generator d) pass the result into the generator scope by storing it as a property on `stop` instead of calling `resultResolver`. – Bergi Oct 28 '19 at 23:38