I have a function that streams data in batches via a callback.
Each batch will await the callback function before fetching another batch and the entire function retu
Would it work if there will be typescript solution?
It should handle condition when callback is called faster then promise is resolved a couple of times.
Callback can be a method that has this signature callback(error, result, index)
It is set to finish when callback is called with no arguments.
Usage:
asAsyncOf(this.storage, this.storage.each);
Solution:
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 };
No, I don't think there's a way to implement this transformation in a way that's easy to understand and easy to follow. However, I would recommend to drop the deferred
s (you're never reject
ing anyway) and just use the promise constructor. Also I'd rather implement an asynchronous generator right away.
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;
}
}
You need a event bucket, here is an example:
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);
}
})();