【问题标题】:Using Bluebird Promises, how to solve this with the deferred anti-pattern?使用 Bluebird Promises,如何使用延迟反模式解决这个问题?
【发布时间】:2017-02-14 14:26:38
【问题描述】:

我正在学习 Bluebird 的 promises,并且我正在尝试学习不再使用任何 Deferred()。 下面的代码按预期 100% 正确运行。 但这对我来说是一个关于重构代码以正确使用 Bluebird Promise 而不是使用 Deferred 解决方案的练习。 我正在尝试学习以不同的方式(正确地)思考 Promise,但经过多次尝试,我仍然无法弄清楚如何在下面 没有 Deferreds 的帮助下解决这个特定问题。

有人有想法吗?

运行方法如下:

1) 在您的控制台中运行此程序。它将启动一个使用 8080 端口的 websocket 服务器。

2) 然后在另一个控制台窗口中再次运行它。在尝试使用端口 8080 失败 3 次后,该端口将启动并使用端口 8081。

// Initialization stuff
const WebSocket = require('ws');
var wsServer;

// Main Program
// =================================================================
tryCreateWebsocket().then(
    function(){
        console.log("Websocket succesfully initialized.");
    },
    function(){
        console.log("Websocket startup has failed!");
    }
);
// =================================================================



// Helper function: Creating a websocket, with a port as parameter
function createWebsocket(port){
    return new Promise(function(resolve, reject){

        wsServer = new WebSocket.Server({
          perMessageDeflate: false, 
          port: port
        });

        wsServer.on("error", reject);
        wsServer.on("listening", resolve); 
    });
}


// Main function: I try to create a websocket on 5 different ports with a resursive function
function tryCreateWebsocket(attempt, myMainDfd){

    if(typeof attempt === "undefined"){
        attempt = 1;
        myMainDfd = deferred();
    }

    var ports = [8080, 8080, 8080, 8081, 8082]; // In the 2nd client, this should fail until port 8081
    var curPort = ports[attempt - 1];

    var maxAttempts = 5;


    createWebsocket(curPort)
        .then(
            function(){
                myMainDfd.resolve(); // Success
            },
            function(err){ // Error, retry
                if(attempt != maxAttempts){
                    console.log("- attempt " + attempt + " failed. Retry");
                    tryCreateWebsocket(++attempt, myMainDfd);
                }else{
                    myMainDfd.reject();
                }
            }
        );

    return myMainDfd.promise;
}


// Helper Function: I'm still using deferreds for now
function deferred() {
        var resolve, reject;
        var promise = new Promise(function() {
                resolve = arguments[0];
                reject = arguments[1];
        });
        return {
                resolve: resolve,
                reject: reject,
                promise: promise
        };
}

【问题讨论】:

  • this fiddle 这样的东西会起作用吗?
  • @JaromandaX - 我刚刚注意到你的评论。它似乎不起作用,但尽管它看起来非常短而整洁,但我很难弄清楚它:-)
  • 抱歉,我犯了两个小错误
  • 应该是jsfiddle.net/pk50ks04/2 - 包含“转译”版本的代码

标签: node.js promise bluebird


【解决方案1】:

在使用 Promise 编程的 3 年中,我只发现了一种情况,即使用 deferred 可以让我的代码更简单。我得出的结论是,这是一种非常罕见的情况。通过学习正确的技术(在此使用链接),人们几乎总能避免它们,并最终得到更简单的代码,从而减少相当常见的错误(例如不完整的错误传播或未捕获的异常)。

在这种特殊情况下,您可以通过从 .then() 处理程序中返回一个新的 Promise 将您的后续尝试链接到前一个 Promise。这允许您从连接函数返回一个承诺,但在未来的尝试中保持该承诺(推迟其最终解决方案),直到未来的一些重试尝试成功或直到您用完重试尝试。

你可以这样做。对connect() 函数内部发生的事情进行特别尝试。

function tryCreateWebsocket(){

    var attempt = 1;
    var ports = [8080, 8080, 8080, 8081, 8082];
    var maxAttempts = ports.length;

    function connect() {
        var curPort = ports[attempt - 1];
        return createWebsocket(curPort).catch(function(err){ // Error, retry
            if(attempt < maxAttempts){
                console.log("- attempt " + attempt + " failed. Retry");
                ++attempt;

                // chain next attempt onto previous promise
                return connect();
            } else {
                // reject here with no more retries
                throw new Error("max retry attempts exceeded without successful connection");
            }
        });
    }

    // start trying to connect, return a promise
    // errors will be caught and subsequent retries will be chained
    // onto this first promise until it either succeeds or runs out
    // of retry attempts
    return connect();
}

// Main Program
// =================================================================
tryCreateWebsocket().then(function(wsServer){
    console.log("Websocket succesfully initialized.");
    // server instance is valid here, use it for further code
},function(){
    console.log("Websocket startup has failed!");
});
// =================================================================



// Helper function: Creating a websocket, with a port as parameter
function createWebsocket(port){
    return new Promise(function(resolve, reject){

        wsServer = new WebSocket.Server({
          perMessageDeflate: false, 
          port: port
        });

        wsServer.on("error", reject);
        wsServer.on("listening", function() {
           resolve(wsServer);
        }); 
    });
}

注意,我更改了设计以使 wsServer 实例成为返回的承诺的解析值。然后,您不依赖副作用来设置更高范围的变量。您可以从已解决的 Promise 中获取它,并在您知道它有效时将其存储在您想要的位置。

【讨论】:

  • 谢谢,我在这里学到了很多东西。我之前一直在抛出错误,但我已经离开了那条路。但现在我看到了你是如何做到的。我现在已经将你的代码原理添加到我的代码中了。
  • 我想现在一切都清楚了。这一切都很好,我学到了一些关键原则。我已将您的解决方案标记为答案,我想这就是它的工作方式。 (经过多年的潜伏,这是我第一次在这个网站上发布一些东西)非常感谢您的建议!
  • @johndoe - 是的,这就是它在这里的工作方式。很高兴能提供帮助。
  • @jfriend00 - 与我计划发布的答案相比,您的方法很好而且简洁。将我的答案与您的答案进行比较时,我为自己感到羞耻。
  • @Simon - 把它贴出来让我们讨论一下:-)
【解决方案2】:

这是我想出的一种可能的解决方案。你怎么看?它仍然使用了总共​​ 2 个 promise(一个在 createWebsocket 函数中,一个在下面的 tryCreateWebsocket 函数中)。

function tryCreateWebsocket(){

    var lstPorts = [8080, 8080, 8080, 8081, 8080];
    return new Promise(function(resolve, reject){

        function next(port) {

            createWebsocket(port)
                .then(
                    resolve,
                    function(){ // Reject, but not until you've tried a little
                        console.log("Port "+port+" failed. I might try the next port.");
                        // rejected
                        if(lstPorts.length >= 1){
                            next( lstPorts.shift() )
                        }else{
                            reject(); // do reject
                        }

                    }
                );
        }

        next( lstPorts.shift() ); // Start the loop
    });
}

【讨论】:

  • 这通常被认为是promise anti-pattern,您在其中不必要地包装了您可能刚刚在另一个手动创建的承诺中返回的承诺。这样做很容易在错误处理或错误传播中出错。仅供参考,无论何时您看到.then(resolve),您都可能只是退回了承诺并避免将其包装在额外的承诺中。这总是一个信号,表明可能有更好的方法。您可以看到我如何在答案中使用链接来避免将其全部包装在另一个承诺中。
猜你喜欢
  • 2013-09-07
  • 2019-05-26
  • 2016-07-09
  • 2015-05-13
  • 1970-01-01
  • 2015-08-25
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多