Javascript 一次消耗3个元素的AsyncGenerator?

Javascript 一次消耗3个元素的AsyncGenerator?,javascript,typescript,asynchronous,promise,generator,Javascript,Typescript,Asynchronous,Promise,Generator,我正在尝试编写一个函数来限制并发发生的数量。它看起来像这样: 异步函数*genSleep(){ 产生睡眠(1000,'A'); 产量睡眠(2000,'B'); 产生睡眠(1000,'C'); 产生睡眠(500,'D'); 产生睡眠(1500,'E'); 产生睡眠(0,'F'); } 异步函数main(){ const t=Date.now(); 并发(genSleep(),3,异步(字母,idx)=>{ log(idx,字母,'start',Date.now()-t); 等待睡眠(500);

我正在尝试编写一个函数来限制并发发生的数量。它看起来像这样:


异步函数*genSleep(){
产生睡眠(1000,'A');
产量睡眠(2000,'B');
产生睡眠(1000,'C');
产生睡眠(500,'D');
产生睡眠(1500,'E');
产生睡眠(0,'F');
}
异步函数main(){
const t=Date.now();
并发(genSleep(),3,异步(字母,idx)=>{
log(idx,字母,'start',Date.now()-t);
等待睡眠(500);
log(idx,字母,'finish',Date.now()-t);
})
输出应为:

0 A start 1000
1 C start 1000
0 A end 1500
1 C end 1500
2 B start 2000
3 D start 2000 // after A finishes, D is queued. It takes 500ms to fire so it starts at 2000
2 B end 2500
3 D end 2500
5 F start 2500
4 E start 3000 // after C finishes at 1500, E is queued. Starts after another 1500ms
5 F end 3000
我想..我很难理解它,但关键是回调应该以“并行”方式最多运行3次(我知道JS是单线程的)

我当前的实现如下,但我不能完全正确。用TypeScript编写,但JS答案很好



export const sleep=(ms:number,ret:any=ms)=>newpromise(r=>setTimeout(()=>r(ret),ms));
接口承诺容器扩展了承诺{
解析(值:TValue):无效
拒绝(理由:叛国):无效
}
函数makePromise(){
常量funcs:any={};
持续承诺=新承诺((解决、拒绝)=>{
funcs.resolve=解析;
funcs.reject=拒绝;
})
Object.assign(承诺、函数);
作为承诺人返还承诺;
}
并发导出异步函数(生成器:AsyncGenerator,maxConcurrent:number=10,回调:(item:T,idx:number)=>Promise){
let pending=new Set();
常量错误:错误[]=[];
让一切都完成=错误;
设idx=0;
对于(;;){
常数p=makePromise();
待决。添加(p);
p、 最后(()=>{
待决。删除(p);
})
console.log('next');
generator.next().then({done,value})=>{
//console.log('generated',value,done);
如果(完成){
//console.log('doneee')
p、 解决();
全部完成=正确;
}否则{
回调(值,idx++)。捕获(错误=>{
错误。推送(错误);
}).最后(()=>{
p、 解决();
})
}
})
//console.log('pending.size',pending.size)
如果(pending.size>=maxConcurrent){
等待承诺。比赛(待定);
}
如果(全部完成){
如果(待定。大小)等待承诺。全部结算(待定);
返回错误;
}
}
}

如果
genSleep()

import * as fs from 'fs';
import {promisify} from 'util';
import {join} from 'path';

const readDir = promisify(fs.readdir);
const readFile = promisify(fs.readFile);

export {readFile};

export async function* readDirR(path: string): AsyncGenerator<string> {
    const entries = await readDir(path, {withFileTypes: true});
    for (let entry of entries) {
        const fullPath = join(path, entry.name);
        if (entry.isDirectory()) {
            yield* readDirR(fullPath);
        } else {
            yield fullPath;
        }
    }
}
import*作为来自“fs”的fs;
从“util”导入{promisify};
从“路径”导入{join};
const readDir=promisify(fs.readDir);
const readFile=promisify(fs.readFile);
导出{readFile};
导出异步函数*readDirR(路径:字符串):AsyncGenerator{
const entries=await readDir(路径,{withFileTypes:true});
for(让条目的输入){
const fullPath=join(路径,entry.name);
if(entry.isDirectory()){
收益率*readDirR(全路径);
}否则{
让路;
}
}
}

读取目录需要一些时间,然后处理每个文件需要几秒钟。我想在读取整个目录之前开始处理这些文件,但我不想一次处理多个文件。处理也是异步的,因此我可以一次处理多个文件。

知道AsyncGenerator不会工作中,我想到了这个:

export async function consume<T>(generator: Generator<Promise<T>>, maxConcurrent: number, callback: (item: T, idx: number) => Promise<void>): Promise<Error[]> {
    const errors: Error[] = [];
    const pending = new Set<Promise<unknown>>();
    let i = 0;

    for (; ;) {
        const {done, value} = generator.next();

        if (done) {
            if (pending.size) await Promise.allSettled(pending);
            return errors;
        }

        const p = value.then((v: T) => callback(v, i++))
            .catch((err: Error) => {
                errors.push(err);
            }).finally(() => {
                pending.delete(p);
            })

        pending.add(p);

        if (pending.size >= maxConcurrent) {
            await Promise.race(pending);
        }
    }
}
输出:

queue A
queue B
queue C
0 A start 1002
1 C start 1004
0 A finish 1504
queue D
1 C finish 1506
queue E
2 B start 2001
3 D start 2006
2 B finish 2504
queue F
3 D finish 2508
4 F start 2509
5 E start 3007
4 F finish 3011
5 E finish 3510
这正是我所期待的。耶


异步版本实际上也可以正常工作:

export async function consumeAsync<T>(generator: AsyncGenerator<T>, maxConcurrent: number, callback: (item: T, idx: number) => Promise<void>): Promise<Error[]> {
    const errors: Error[] = [];
    const pending = new Set<Promise<unknown>>();
    let i = 0;

    for (; ;) {
        const {done, value} = await generator.next();

        if (done) {
            if (pending.size) await Promise.allSettled(pending);
            return errors;
        }

        const p = callback(value, i++)
            .catch((err: Error) => {
                errors.push(err);
            }).finally(() => {
                pending.delete(p);
            })

        pending.add(p);

        if (pending.size >= maxConcurrent) {
            await Promise.race(pending);
        }
    }
}
export-async函数consumerasync(生成器:AsyncGenerator,maxConcurrent:number,callback:(item:T,idx:number)=>Promise):Promise{
常量错误:错误[]=[];
const pending=新集合();
设i=0;
对于(;;){
const{done,value}=wait generator.next();
如果(完成){
如果(待定。大小)等待承诺。全部结算(待定);
返回错误;
}
常量p=回调(值,i++)
.catch((错误:Error)=>{
错误。推送(错误);
}).最后(()=>{
待决。删除(p);
})
待决。添加(p);
如果(pending.size>=maxConcurrent){
等待承诺。比赛(待定);
}
}
}

正如@georg所提到的,您不能使用异步生成器函数,它们总是按顺序运行。此外,您应该避免(
makePromise
),只需在
generator.next()
上使用承诺链。除此之外,我认为您的代码看起来不错。
export async function consumeAsync<T>(generator: AsyncGenerator<T>, maxConcurrent: number, callback: (item: T, idx: number) => Promise<void>): Promise<Error[]> {
    const errors: Error[] = [];
    const pending = new Set<Promise<unknown>>();
    let i = 0;

    for (; ;) {
        const {done, value} = await generator.next();

        if (done) {
            if (pending.size) await Promise.allSettled(pending);
            return errors;
        }

        const p = callback(value, i++)
            .catch((err: Error) => {
                errors.push(err);
            }).finally(() => {
                pending.delete(p);
            })

        pending.add(p);

        if (pending.size >= maxConcurrent) {
            await Promise.race(pending);
        }
    }
}