一个Promise触发零个或多个Promises

时间:2016-05-13 22:12:46

标签: javascript node.js promise

简短的问题,相当抽象:

流程A和流程B都会返回承诺。

流程B需要调用流程A零次或多次才能解决其承诺。这种事情的正确模式是什么?

更长,但更具体,问题:

想象一下,我有一个produceMsg进程创建一个promise,当解析时,会产生一个n字节的缓冲区。 (也许它通过网络连接获取字节。也许偶尔它会产生错误。)这是一个测试夹具,它可以做到这一点:

// promise to yield a buffer of up to 20 bytes and an occasional error
function produceMsg() {
    return new Promise(function(resolve, reject) {
        var n = Math.floor(Math.random() * 20);
        if (n === 0) {       // generate an error sometimes...
            reject("some error");
        } else {             // create a buffer with n random bytes
            var msg = createRandomMessage(n);
            console.log('generating', msg);
            resolve(msg);
        }
    });
};

// helper method: create a buffer of n random bytes
function createRandomMessage(n) {
    return Buffer(Array(n).fill().map(function(e) {
        return Math.floor(Math.random() * 256); }));
}

现在假设我有consume这些承诺的方法:

function consume() {
    setInterval(function() {
        produceMsg()
            .then(function(b)  { console.log("==> fetched", b); })
            .catch(function(b) { console.log("==> error", b); })
                ;
    }, 200);
}

测试它,它按预期工作:

generating <Buffer c8 71 6a 3f fe 84 05 71>
==> fetched <Buffer c8 71 6a 3f fe 84 05 71>
generating <Buffer 03 66>
==> fetched <Buffer 03 66>
==> error some error
generating <Buffer 49 d2 4f 6f d6 bc 48 cf e7 db f7 f6 f7 e2 e7 5c df>
==> fetched <Buffer 49 d2 4f 6f d6 bc 48 cf e7 db f7 f6 f7 e2 e7 5c df>
generating <Buffer ef 6c 5f 3c 2f c8 b1 ff b5 eb 13 0e 76 d8>
==> fetched <Buffer ef 6c 5f 3c 2f c8 b1 ff b5 eb 13 0e 76 d8>

但现在我告诉我必须将传入的数据包重新组合成10个字节的块。 (好吧,这是一个人为的例子,但我必须重新组合数据包。)

所以我需要一个中间reframer对象,它创建一个承诺,在它解析时返回10个字节的数据包。如果它没有10个字节,则需要从produceMsg进程收集字节,直到它累积到足够多。

我修改后的consume方法可能如下所示:

function consume() {
    var reframer = new Reframer(produceMsg);
    setInterval(function() {
        reframer.read()
            .then(function(b)  { console.log("==> fetched", b); })
            .catch(function(b) { console.log("==> error", b); })
                ;
    }, 200);
}

...并使用与上面相同的数据,我希望输出看起来像这样:

generating <Buffer c8 71 6a 3f fe 84 05 71>
generating <Buffer 03 66>
=> fetched <Buffer c8 71 6a 3f fe 84 05 71 03 66>
=> error some error
generating <Buffer 49 d2 4f 6f d6 bc 48 cf e7 db f7 f6 f7 e2 e7 5c df>
=> fetched <Buffer 49 d2 4f 6f d6 bc 48 cf e7 db>
generating <Buffer ef 6c 5f 3c 2f c8 b1 ff b5 eb 13 0e 76 d8>
=> fetched <Buffer f7 f6 f7 e2 e7 5c df ef 6c 5f>
=> fetched <Buffer 3c 2f c8 b1 ff b5 eb 13 0e 76>

(注意最后两行reframer生成了两条消息而没有调用produceMsg,因为它已经累积了足够的字节。)

问题:reframer.read()的结构是什么?

我还没弄明白如何构建reframer.read()方法的内容。有没有一种很好的模式来做这种事情,一个承诺有条件地链接要求零或更多的承诺?

(注意:我&#39; m 询问如何concatslice缓冲区等等 - 我已经有了代码来执行此操作。我和# 39;坚持是Promises生成和解决的控制流程。)

3 个答案:

答案 0 :(得分:3)

我认为你所追求的是这样的:

function Reframer(producer) {
    var buffer;     // for accumulation

    this.read = function() {
        if (buffer.length >= max) {
            frame = first "max" bytes removed from buffer
            return Promise.resolve(frame)
        } else {
            return producer.read().then((read_data) => {
                append read_data to buffer
                return this.read();  // pseudo-recurse
            });
        }
    };
}

即。如果累积的缓冲区中已经有足够的字节,则从该缓冲区中删除所需的字节数,并返回一个立即用这些字节解析的promise。

否则,要求生产者发送更多数据,将其添加到缓冲区,然后再递归到上面的步骤。

如果生产者生成任何.reject,那么 只会传播并导致.read方法拒绝。

[{1}}分支的替代版本可能会直接解析新数据,如果它足以填充缓冲区以避免需要将整个新数据附加到缓冲区在下一个递归步骤中立即将其删除]

答案 1 :(得分:1)

我真的很喜欢Alnitak's回答,而且我有动力将它改写为一般模式。 (其他任何人都可以随意改进)。

如果你有一个产生Promise的进程B,需要调用Promise-production进程A零次或多次才能解决它的承诺,一般模式是:

function B(A) {
    if (have_necessary_data()) {
        return Promise.resolve(processed_data());
    } else {
        return A().then(function(incoming_data) { 
            do_something_with(incoming_data);
            return B(A);
         });
    }
};

答案 2 :(得分:0)

我认为不可能做多次承诺解决,虽然你根本无法解决它,一次返回多个缓冲区的一个解决方案是使用格式化缓冲区数组解决最终承诺,而不是消费者过去数组:

function Reframer (msg) {
    this.msg = msg
    this.stash = []
}

Reframer.prototype.read = function (){
    return this.msg()
        .then((b) => {
            this.stash.push(b)

            // Do check here for stashed Buffers and apply appropriate logic to
            // slice and dice them if there is something to return do this with promise
            // Assuming I have define `chunk` variable which is array of 10 bytes Buffer(s)
            // And if there is anything else left it has to be stashed appropriately for future reuse if any.

            var chunk = [Buffer(10), Buffer(10)]

            return Promise.resolve(chunk)
        })
        .catch((err) => Promise.reject(err))
}

和消费者:

function consume() {
    var reframer = new Reframer(produceMsg);
    setInterval(function() {
        reframer.read()
            .then(function(abs)  { abs.forEach((b) => console.log("==> fetched", b)); })
            .catch(function(err) { console.log("==> error", err); });
    }, 200);
}

同样'hard'setInterval可能会产生某种竞争条件。更好的模式IMHO将通过setTimeout以递归方式调用函数。

var reframerOne = new Reframer(produceMsg);
function consumeOne() {
    setTimeout(function() {
        reframerOne.read()
            .then(function(abs)  { 
                abs.forEach((b) => console.log("==> fetched", b));
                consumeOne();
            })
            .catch(function(err) { console.log("==> error", err); });
    }, 200);
}

虽然你可以在这里举例说明。所以在这种情况下澄清一下。