![](/img/trans.png)
[英]Node.js : how to handle callback with async.js and websocket?
[英]How to convert Node.js async streaming callback into an async generator?
我有一個通過回調批量傳輸數據的函數。
每個批次將在獲取另一個批次之前等待回調函數,並且整個函數返回一個承諾,該承諾在所有批次完成時解析。
(我使用 TypeScript 注釋來幫助提高可讀性)
async function callbackStream(fn: (batch: Array<number>) => Promise<void>) {}
如何將此函數轉換為一次產生一個值的異步生成器?
async function* generatorStream(): AsyncIterableIterator<number> {}
事實證明,這是一項非常艱巨的任務。
我已經解決了這個問題並且我已經構建了一些有用的東西,但是它非常復雜,我無法證明合並這些代碼並讓我團隊中的其他人處理它是合理的。
這是我目前的實現:
我正在使用這個幫助函數創建了一個“延遲”的承諾,它有助於在回調周圍傳遞承諾。
interface DeferredPromise<T> {
resolve: (value: T) => void
reject: (error: any) => void
promise: Promise<T>
}
function deferred<T>(): DeferredPromise<T> {
let resolve
let reject
const promise = new Promise<T>((res, rej) => {
resolve = res
reject = rej
})
return {
resolve: resolve as (value: T) => void,
reject: reject as (error: any) => void,
promise,
}
}
接下來,我有這個邏輯毛球,將承諾回調線性化為一個鏈,其中每個承諾使用 next 函數解析一個批次,該批次將返回另一個獲取下一批的承諾。
type Done = { done: true }
type More = { done: false; value: Array<number>; next: () => Promise<Result> }
type Result = More | Done
async function chainedPromises() {
let deferred = PromiseUtils.deferred<Result>()
callbackStream(async batch => {
const next = PromiseUtils.deferred<null>()
deferred.resolve({
done: false,
value: batch,
next: () => {
deferred = PromiseUtils.deferred<Result>()
next.resolve(null)
return deferred.promise
},
})
await next.promise
}).then(() => {
deferred.resolve({ done: true })
})
return deferred.promise
}
從這里開始,創建一個一次生成一個項目的生成器並不是很困難:
async function* generatorStream(): AsyncIterableIterator<number> {
let next = chainedPromises
while (true) {
const result = await next()
if (result.done) {
return
}
for (const item of result.value) {
yield item
}
next = result.next
}
}
我想我們都同意中間的chainedPromises
函數非常混亂和復雜。 有什么方法可以以一種易於理解和易於理解的方式將callbackStream
轉換為generatorStream
嗎? 如果庫已經完善,我不介意使用它,但我也很欣賞第一性原理的簡單實現。
不,我認為沒有一種方法可以以易於理解和遵循的方式實現這種轉換。 但是,我建議刪除deferred
s(您永遠不會reject
ing )並只使用 promise 構造函數。 此外,我寧願立即實現一個異步生成器。
function queue() {
let resolve = () => {};
const q = {
put() {
resolve();
q.promise = new Promise(r => { resolve = r; });
},
promise: null,
}
q.put(); // generate first promise
return q;
}
function toAsyncIterator(callbackStream) {
const query = queue();
const result = queue();
const end = callbackStream(batch => {
result.put(batch);
return query.promise;
}).then(value => ({value, done: true}));
end.catch(e => void e); // prevent unhandled promise rejection warnings
return {
[Symbol.asyncIterator]() { return this; },
next(x) {
query.put(x);
return Promise.race([
end,
result.promise.then(value => ({value, done:false})
]);
}
}
}
async function* batchToAsyncIterator(batchCallbackStream) {
for await (const batch of toAsyncIterator(batchCallbackStream)) {
// for (const val of batch) yield val;
// or simpler:
yield* batch;
}
}
您需要一個事件存儲桶,這是一個示例:
function bucket() {
const stack = [],
iterate = bucket();
var next;
async function * bucket() {
while (true) {
yield new Promise((res) => {
if (stack.length > 0) {
return res(stack.shift());
}
next = res;
});
}
}
iterate.push = (itm) => {
if (next) {
next(itm);
next = false;
return;
}
stack.push(itm);
}
return iterate;
}
;(async function() {
let evts = new bucket();
setInterval(()=>{
evts.push(Date.now());
evts.push(Date.now() + '++');
}, 1000);
for await (let evt of evts) {
console.log(evt);
}
})();
如果有打字稿解決方案,它會起作用嗎?
當回調被調用得更快然后承諾被解決幾次時,它應該處理條件。 回調可以是具有此簽名的方法callback(error, result, index)
當沒有參數調用回調時callback(error, result, index)
它被設置為完成。 用法:
asAsyncOf(this.storage, this.storage.each);
解決方案:
function asAsyncOf<T1, T2, T3, T4, Y>(c, fn: { (a: T1, a1: T2, a2: T3, a3: T4, cb: { (err?, res?: Y, index?: number): boolean }): void }, a: T1, a1: T2, a2: T3, a3: T4): AsyncGenerator<Y>
function asAsyncOf<T1, T2, T3, Y>(c, fn: { (a: T1, a1: T2, a2: T3, cb: { (err?, res?: Y, index?: number): boolean }): void }, a: T1, a1: T2, a3: T3): AsyncGenerator<Y>
function asAsyncOf<T1, T2, Y>(c, fn: { (a: T1, a1: T2, cb: {(err?, res?: Y, index?: number): boolean}): void}, a: T1, a1: T2): AsyncGenerator<Y>
function asAsyncOf<T, Y>(c, fn: { (a: T, cb: { (err?, res?: Y, index?: number): boolean }): void }, a: T): AsyncGenerator<Y>
function asAsyncOf<Y>(c, fn: { (cb: {(err?, res?: Y, index?: number): boolean}): void}): AsyncGenerator<Y>
async function* asAsyncOf(context, fn, ...args) {
let next = (result?) => { };
let fail = (err) => { };
let finish = {};
const items = [];
let started = true;
try {
fn.apply(context, [...args, function (err, result, index) {
const nextArgs = [].slice.call(arguments, 0);
if (nextArgs.length === 0) {
started = false;
next(finish);
return true;
}
if (err) {
fail(err);
return true;
}
items.push(result);
next(result);
}]);
} catch (ex) {
fail(ex);
}
while (true) {
const promise = started ? new Promise((resolve, error) => {
next = resolve;
fail = error;
}) : Promise.resolve(finish);
const record = await promise;
if (record === finish) {
while (items.length) {
const item = items.shift();
yield item;
}
return;
}
while (items.length) {
const item = items.shift();
yield item;
}
}
}
export { asAsyncOf };
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.