Javascript 一个承诺触发零个或多个承诺 简短而抽象的问题:

Javascript 一个承诺触发零个或多个承诺 简短而抽象的问题:,javascript,node.js,promise,Javascript,Node.js,Promise,流程A和流程B都是退货承诺 流程B需要调用流程A零次或多次才能解析其承诺。这类事情的正确模式是什么 更长但更具体的问题: 假设我有一个produceMsg进程,该进程创建了一个承诺,当解析该承诺时,将产生一个n字节的缓冲区。(可能是通过网络连接获取字节,也可能偶尔会产生错误。)下面是一个测试夹具: // promise to yield a buffer of up to 20 bytes and an occasional error function produceMsg() { r

流程
A
和流程
B
都是退货承诺

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

更长但更具体的问题: 假设我有一个
produceMsg
进程,该进程创建了一个承诺,当解析该承诺时,将产生一个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); }));
}
现在想象一下,我有一种方法来消费那些承诺:

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>
。。。使用与上面相同的数据,我希望输出如下所示:

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>
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
            });
        }
    };
}
生成
生成
=>获取
=>一些错误
生成
=>获取
生成
=>获取
=>获取
(注意,在最后两行中,
reframer
生成了两条消息,但没有调用
produceMsg
,因为它已经积累了足够的字节。)

问题:reframer.read()的结构是什么? 我还没有弄清楚如何构造
reframer.read()
方法的核心。有没有一个好的模式来做这种事情,其中一个承诺有条件地链接调用零个或多个承诺


(注意:我不是问如何
concat
slice
缓冲区等等——我已经有了这样做的代码。我坚持的是
承诺的生成和解析的控制流)

我想你想要的是这样的:

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>
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
            });
        }
    };
}
i、 e.如果累积的缓冲区中已经有足够的字节,则从该缓冲区中移除所需的字节数,并返回一个立即用这些字节解析的承诺

否则,请生产者发送更多数据,将其添加到缓冲区,然后返回到上面的步骤

如果制作人生成了任何
.reject
,则它应该只是传播并导致
.read
方法也被拒绝


[另一个版本的
else
分支可能会直接使用新数据进行解析,前提是它足以填充缓冲区,以避免需要将整个新数据追加到缓冲区,而在下一个递归步骤中又立即将其删除]

我认为不可能进行乘法承诺解析,虽然您根本无法解决此问题,但一次返回多个缓冲区的一种解决方案是使用格式化缓冲区数组解析最终承诺,然后在消费者中遍历该数组:

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);
}
此外,“硬”设置间隔可能会造成某种比赛条件。更好的模式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);
}
尽管你可以在这里举个例子。所以,在这个例子中,我只是澄清一下。

我真的很喜欢这个答案,我有动力把它作为一个一般的模式来改写。(其他任何人都可以随意对此进行改进)

如果您有一个承诺生成流程
B
,需要调用承诺生成流程
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);
         });
    }
};

你似乎在寻找递归。@Bergi好吧,伪递归……Dmi3y:我还没有验证过,但我认为你的解决方案存在一个问题:假设制作人每次产生40比特。但重新格式化程序会阻塞,直到生产者生成一条消息,然后它只能生成一个10字节的数据包,所以它会越来越落后。我认为阿尔尼塔克的回应是有希望的。好吧,如果制作人一次产出40比特,那么reframer的承诺将通过一次4个改变缓冲区的数组来解决。。。留下空的储藏室准备下一个卡拉赫-我明白了。谢谢你的澄清,就像冠军一样。我没有想到我可以
返回Promise.resolve()
——这是缺少的部分。(是的,制作人的
.reject
已正确传播。)停止印刷!单元测试发现了一个问题:假设producer用11字节的数据包解决了这个问题。每次Reframer调用producer.read(),它都会收到相同的数据包,从而产生重复的数据。还是我遗漏了一些基本的东西?@fearless\u fool你真的从临时缓冲区中删除了10个字节(那些最后出现在
帧中的字节)?啊,或者你只希望
制作人
返回一个承诺吗?在我上面的代码中,我有一个
读取
方法,每次调用时都会生成一个新承诺。啊,这是我的一个概念缺陷:显然
读取
方法需要在每次调用时返回一个新承诺(因为承诺的总合同是评估其确定的价值。)我将不得不进一步思考这个问题。这很有趣。试图更好地理解这个模式。那么,在本例中,
B
函数对应的是什么?它是示例中的
Reframer
类吗?然后消费者的代码将是什么样子?…是-
B()
对应的(大致)由于
B()
返回一个承诺,消费者的代码将返回值视为任何普通承诺。但是请参见我上面的“停止印刷”注释--
a()
每次调用都必须返回一个新的承诺,否则这就行不通了。明白了,我很好奇最终的解决方案会是什么样子。追求这个解决方案值得称赞。简短而模糊的答案是:当解决方案真的需要异步“推送”时,不要试图创建一个返回值的同步“拉”函数执行副作用的函数。如果你能理解我的意思,请加分!:)哈哈,我想我明白了,但不要试图让我解释:)