Я использую 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 Silva

2 ответа

Я немного изменил код @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 };

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

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 'request body'
}


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