Я использую Axios для вызова конечной точки api, у которой есть индикатор выполнения. Я хотел бы преобразовать onUploadProgress в генератор.

Есть ли способ преобразовать этот код

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 });

На что-то вроде этого

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 };

Проблема в доходности внутри onUploadProgress, я думал, что есть способ использовать его, например, когда вы хотите преобразовать обратный вызов в обещание, которое вы используете

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

Возможно, есть какой-нибудь действенный способ сделать это для генераторов, таких как

new Generator(next => fn(next));
1
Leonardo da Silva 27 Окт 2019 в 13:42
Как вы можете себе представить, что этот «генератор» будет потреблен? Без этого ваш вопрос непонятен.
 – 
Bergi
27 Окт 2019 в 21:34
Основываясь на ответах на данный момент - кажется, генераторы не совсем подходящий инструмент для работы - придерживайтесь обратного вызова или, возможно, шаблона pubsub
 – 
Bravo
27 Окт 2019 в 23:30
Генератор async может отлично справиться с этой задачей, но OP еще не заявил, что это именно то, что он имел в виду.
 – 
Bergi
29 Окт 2019 в 00:05
- Я не понимаю, как асинхронный генератор поможет при попытке получить значения из обратного вызова, который вызывается несколько раз - даже если бы вы могли, код был бы беспорядочным и громоздким для поддержки
 – 
Bravo
29 Окт 2019 в 00:53
Это вполне выполнимо, хотя вы правы, что потребуется очередь для обработки каждого аргумента обратного вызова, когда это происходит быстрее. чем асинхронная итерация.
 – 
Bergi
29 Окт 2019 в 01:05

2 ответа

Вы можете обновить локальную переменную в обработчике выполнения и опрашивать ее в цикле, пока запрос не будет завершен. Вот набросок:

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()

Вот еще один вариант, без опроса и лучшего 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()
2
georg 27 Окт 2019 в 22:38
На самом деле вы только что переместили обратный вызов прогресса: D
 – 
Bravo
27 Окт 2019 в 14:18
1
Нет, не проводи опрос.
 – 
Bergi
27 Окт 2019 в 21:37
Спасибо за обновление, оно выглядит намного лучше :-) Я бы также а) поместил вызов factory() (и в основном остальной код) внутри функции генератора б) поместил genPromise = … внутри цикл, чтобы избежать дублирования c) полностью отбросить resultPromise и вместо этого return результат от генератора d) передать результат в область действия генератора, сохранив его как свойство в stop вместо звонит resultResolver.
 – 
Bergi
29 Окт 2019 в 02:38

Я немного изменил код @georg, чтобы можно было использовать этот подход с любой функцией и иметь интерфейс, который ближе к тому, что было запрошено.

async function* createGenerator<T>(factory: (
    next: (value: T) => void, 
    complete: () => void) => void,
): AsyncIterableIterator<T> {
    let completed = false;
    let value: { value: T } | null = null;

    factory(
        (v: T) => {
            value = { value: v };
        }, 
        () => {
            completed = true;
        },
    );

    while (!completed) {
        if (value != null) {
            yield (value as { value: T }).value;
            value = null;
        }
        value = null;
        await new Promise(resolve => setImmediate(resolve));
    }
}

Так что теперь его можно использовать вот так

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

yield* createGenerator<Progress>((next, complete) => {
    axios.post(uri.serialize(parsedURI), body, {
        headers: { session_id: sessionId },
        onUploadProgress: (progress) => {
            next({ state: 'syncing', progress: progress.loaded / progress.total });
        },
    }).then(() => complete());
});

yield { state: 'syncing', progress: 1 };
-1
Leonardo Silva 27 Окт 2019 в 17:48
Хорошая работа, но вы не справляетесь с результатом обещания, поэтому оно потеряно.
 – 
georg
27 Окт 2019 в 21:15
Опрос с помощью setImmediate совершенно неприемлем.
 – 
Bergi
27 Окт 2019 в 21:35
Я обновил свой ответ, добавив улучшенный API возврата.
 – 
georg
27 Окт 2019 в 22:39