避免多个流的回调地狱

时间:2015-05-20 01:04:06

标签: node.js stream promise

当我打开几个流时,如何避免使用类似递归的结构,我必须得到一个绝对的end事件才能完成逻辑。

var someArray = ['file1', 'file2', 'file3'];

someArray.forEach(function( file ) {
    fs
        .createReadStream( file )
        .pipe( /* do some stuff */ )
        .on('data', function( usageInfo ) {

            // done?

        });
}

我有几个文件,我必须通过tp管道一些进程。如何设置一个事件,告诉我何时完成所有事件? 目前我得到的是每个end事件。

我绝对可以同时启动每个流。我只是需要以某种方式收集结束?

我可以为每个end事件调用一个函数调用并计算它......虽然听起来很讨厌?...

我觉得有一种方法可以用承诺做到这一点,但我不知道如何。

2 个答案:

答案 0 :(得分:3)

使用计数器:

var someArray = ['file1', 'file2', 'file3'];
var still_processing = someArray.length;

someArray.forEach(function( file ) {
    fs.createReadStream( file )
        .pipe( /* do some stuff */ )
        .on('end', function() {
            still_processing--;

            if (!still_processing) {
                // done
            }
        });
}

这是基本机制。此控制流模式由async.js中的async.parallel()函数封装:

var someArray = ['file1', 'file2', 'file3'];
var streams_to_process = [];

someArray.forEach(function( file ) {
    streams_to_process.push(function(callback) {
        var result = "";
        fs.createReadStream( file )
            .pipe( /* do some stuff */ )
            .on('end', function() {
                callback(null, result);
            });
    });
});

async.parallel(streams_to_process, function(err, results) {
    // all done
});

在内部,async.parallel使用闭包中捕获的计数器来跟踪所有异步进程(在本例中为'end'事件)的完成时间。

还有其他库。例如,大多数promise表提供了一个.all()方法,其工作方式相同 - 内部跟踪计数器值并在完成所有操作后触发.then()回调。

答案 1 :(得分:3)

  

我觉得有一种方法可以用承诺做到这一点,但我不知道如何。

是的,有。由于promises确实表示异步值,因此您将获得一个流的结束承诺:

void testStorage() {

    int nFrames = 512;

    int width = 0;
    int height = 0;

    // 8-bit unsigned char images
    Mat frame, floatFrame;

    frame = imread("C:/Matlab code/im.png", CV_LOAD_IMAGE_GRAYSCALE); 

    // convert uchar images to float images
    frame.convertTo(floatFrame, CV_32F, 1.0/255.0f);

    width = frame.step;
    height = frame.rows;

    cout << "width: " << width << " height: " << height << endl;
    float *gpuBuffer;
    float *testImage;

    gpuErrchk( cudaMalloc( (void**) &gpuBuffer, sizeof(float) * width * height * nFrames));         // storage init for buffer
    gpuErrchk( cudaMemset(gpuBuffer, 0, sizeof(float) * width * height * nFrames)); // set mem to 0 

    gpuErrchk( cudaMalloc( (void**) &testImage, sizeof(float) * width * height ));          // storage init for image
    gpuErrchk( cudaMemset(testImage, 0, sizeof(float) * width * height ));  // set mem to 0

    gpuErrchk( cudaMemcpy( testImage, floatFrame.ptr<float>(), sizeof (float) * width * height, cudaMemcpyHostToDevice) );

    // num of threads
    dim3 Threads(width); 
    // num of blocks
    dim3 Blocks(height); 

    for(int i = 0; i < nFrames; i++)
    {
        copySlice2Volume2<<< Blocks, Threads >>> (gpuBuffer, testImage, width, height, i);  

    }

    gpuErrchk( cudaDeviceSynchronize() );    // error here

    printf("Cuda status2: %s\n", cudaGetErrorString( cudaGetLastError() ) );

    gpuErrchk( cudaFree(gpuBuffer) );
    gpuErrchk( cudaFree(testImage) );

    }

可以像var p = new Promise(function(resolve, reject) { fs.createReadStream(file) .on('error', reject) .pipe(/* do some stuff */) .on('end', resolve) .on('error', reject); // call reject(err) when something goes wrong }); 一样使用。

现在,如果您创建多个promises,一个用于数组中的filename,则所有流将并行运行并在完成后解析它们各自的承诺。然后,您可以使用wikipedia entry for Graham scan来收集 - 阅读“等待” - 将每个结果的所有结果转换为一系列结果的新承诺。

p.then(functio(usageInfo) { console.log("stream ended"); })