节点回调地狱+异步通知

Node Callback hell + async advice

我是 node 的新手,最近阅读了大量关于如何最好地避免回调地狱的书。由于我的新手能力,我在我的代码中积累了一些金字塔,所以我点击 google 看看如何避免它。

我正在使用 Salesforce Marketing Cloud 构建应用程序 API...

这是我的场景。

  1. 检查我的帐户中是否存在特定文件夹。
  2. 如果存在 return 文件夹的 ID
  3. 如果它不存在,搜索父文件夹并获取它的 ID
  4. 创建文件夹并使用 ParentFolders ID 作为 属性。

所以在旧学校代码中,这非常简单,只需按顺序执行步骤 1-4。

在节点中,我最终得到了一个带有回调函数的函数。

感谢 Google 我遇到了异步库,但不太确定如何最好地利用它。

这是我的一段代码,它是我从运行服务器的 main.js 文件调用的更大模块的一部分

MyHelper.prototype.test = function(name, req, callback) {

    async.series([
        function(next){
            etHelper.folder_find("dataextension", name, next);  
        },function(next){
            etHelper.folder_find("dataextension", "Data Extensions", next); 
        }
    ],
    function(err, results){
        console.log(results)
    });


};

我应该使用异步的瀑布方法吗?如果是这样,我将如何检查步骤 1 中的 ID,如果它不是 null exit

不确定它是否有帮助,但这是我从我的 ETHelper 方法构建的响应

[ { Success: false,
Status: 'Warning',
Message: 'Folder Not Found',
Type: 'dataextension',
Name: '10513542_SQLHelper_Test',
ID: null },
  { Success: true,
Status: 'OK',
Message: 'Folder Found',
Type: 'dataextension',
Name: 'Data Extensions',
ID: '1040721' } ]

我真的很想在这里学习,但我对所遇到的信息有点不知所措。似乎有 100 种不同的做事方式,很难知道选择哪一种。

最后,我不想搞砸这个 post 但这是我想出的原始方法来处理这个,我知道这是一团糟。

SQLHelper.prototype.checkInstall = function(name, req, callback) {

    var response = new Object();
    var deDone =  false;
    var qDone = false;
    /*
     *********************************************************************************************************
     * 
     * This function will look for the SQLHelper folders. If they are not found they will be added
     *
     *********************************************************************************************************
    */

    //Setup the DEFolder Lookup Filter
    var deFolderParms = {
        objectType: "DataFolder",
        props: ["Name", "ID", "ContentType"],
        filter: {
            leftOperand: {
                leftOperand: 'Name',
                operator: 'equals',
                rightOperand: name
            },
            operator: 'AND',
            rightOperand: 
            {
                leftOperand: 'ContentType',
                operator: 'equals',
                rightOperand: "dataextension"
            }
        }
    };

    //Look for the Folder
    etHelper.getByName(deFolderParms, SoapClient, function(err, deFolderResponse){
        //console.log(deFolderResponse);
        if(deFolderResponse.length <=0)
        {
            //Folder Does not exist so create it
            //But first we need the Parent Folder


            var deParentFolderParms = {
                objectType: "DataFolder",
                props: ["Name", "ID", "ContentType"],
                filter: {
                    leftOperand: {
                        leftOperand: 'Name',
                        operator: 'equals',
                        rightOperand: "Data Extensions"
                    },
                    operator: 'AND',
                    rightOperand: 
                    {
                        leftOperand: 'ContentType',
                        operator: 'equals',
                        rightOperand: "dataextension"
                    }
                }
            };
            etHelper.getByName(deParentFolderParms, SoapClient, function(err, deParentFolderResponse){
                //console.log(deParentFolderResponse);
                if(deParentFolderResponse.length > 0){
                    var newFolderParms = {
                        objectType: "DataFolder", 
                        props: {
                            Name: name,
                            CustomerKey: name,
                            Description: "This folder is only to be used by the SQL Helper Application", 
                            ContentType: "dataextension", 
                            IsActive: true, 
                            IsEditable: true,
                            AllowChildren: false,
                            ParentFolder:{
                                ID: deParentFolderResponse[0].ID    
                            },
                            Client:{
                                ID: req.session.fuel.mid
                            }
                        },
                        options: {}
                    };

                    etHelper.create(newFolderParms, SoapClient, function(err, newFolderResponse){
                        //console.log(newFolderResponse);
                        if(newFolderResponse.length > 0){
                            deDone = true;
                            response.DEFolderID = newFolderResponse[0].NewID;
                            response.Status = true;
                            if(qDone)
                            {
                                callback(response);
                            }
                        }
                        else{
                            response.Status = false;
                            callback(response);
                        }

                    });
                }
                else
                {
                    response.Status = false;
                    callback(response);
                }
            });
        }   
        else
        {
            deDone = true;
            response.DEFolderID = deFolderResponse[0].ID;
            response.Status = true;
            if(qDone)
            {
                callback(response);
            }
        }
    });


    //Setup the DEFolder Lookup Filter
    var qFolderParms = {
        objectType: "DataFolder",
        props: ["Name", "ID", "ContentType"],
        filter: {
            leftOperand: {
                leftOperand: 'Name',
                operator: 'equals',
                rightOperand: name
            },
            operator: 'AND',
            rightOperand: 
            {
                leftOperand: 'ContentType',
                operator: 'equals',
                rightOperand: "queryactivity"
            }
        }
    };

    //Look for the Folder
    etHelper.getByName(qFolderParms, SoapClient, function(err, qFolderResponse){
        //console.log(qFolderResponse);
        if(qFolderResponse.length <=0)
        {
            //Folder Does not exist so create it
            //But first we need the Parent Folder


            var qParentFolderParms = {
                objectType: "DataFolder",
                props: ["Name", "ID", "ContentType"],
                filter: {
                    leftOperand: {
                        leftOperand: 'Name',
                        operator: 'equals',
                        rightOperand: "Query"
                    },
                    operator: 'AND',
                    rightOperand: 
                    {
                        leftOperand: 'ContentType',
                        operator: 'equals',
                        rightOperand: "queryactivity"
                    }
                }
            };
            etHelper.getByName(qParentFolderParms, SoapClient, function(err, qParentFolderResponse){
                //console.log(qParentFolderResponse);
                if(qParentFolderResponse.length > 0){
                    var qnewFolderParms = {
                        objectType: "DataFolder", 
                        props: {
                            Name: name,
                            CustomerKey: name,
                            Description: "This folder is only to be used by the SQL Helper Application", 
                            ContentType: "queryactivity", 
                            IsActive: true, 
                            IsEditable: true,
                            AllowChildren: false,
                            ParentFolder:{
                                ID: qParentFolderResponse[0].ID 
                            },
                            Client:{
                                ID: req.session.fuel.mid
                            }
                        },
                        options: {}
                    };

                    etHelper.create(qnewFolderParms, SoapClient, function(err, qnewFolderResponse){
                        //console.log(qnewFolderResponse);
                        if(qnewFolderResponse.length > 0){
                            qDone = true;
                            response.QueryFolderID = qnewFolderResponse[0].NewID;
                            response.Status = true;
                            if(deDone)
                            {
                                callback(response);
                            }
                        }
                        else{
                            response.Status = false;
                            callback(response);
                        }

                    });
                }
                else
                {
                    response.Status = false;
                    callback(response);
                }
            });
        }   
        else
        {
            qDone = true;
            response.QueryFolderID = qFolderResponse[0].ID;
            response.Status = true;
            if(deDone)
            {
                callback(response);
            }
        }
    });
};

是的,至少有 100 种不同的方法可以解决这个问题,其中许多不一定比其他任何方法都好——这取决于您和您的团队最喜欢什么以及什么最适合您的需求。你真的只能通过经验来判断,同样的解决方案不会每次都是最适合你的。

我建议阅读 callbackhell.com, a manifesto on mitigating callback hell / pyramid of doom 继续阅读 没有 async.

这样的外部库

在您的情况下,使用 async.waterfall 会奏效。它与 async.series 类似,只是它将非错误参数传递给系列中的下一个函数。

async.waterfall([
    function(next){
        etHelper.folder_find("dataextension", name, next);  
    },function(result, next){
        if (null == result.ID) {
            // stop everything
            return next("some error");
        }
        etHelper.folder_find("dataextension", "Data Extensions", next); 
    }
], function (err, results) {
    // handle `err` here if it's not null
});

还要注意 Promises, which are another construct for handling synchronization. There are several node promise libraries out there like bluebird

您的上述代码将以类似的方式工作,除了 folder_find 接受回调参数,它应该 return 一个您在内部拒绝以防出错的承诺。

etHelper.folder_find("dataextension", name)
    .then(function (result) {
        if (null == result.ID) {
            throw new Error("some error");
        }
        return etHelper.folder_find("dataextension", "Data Extensions");
    })
    .then(function (result) {
        console.log(result);
    })
    .catch(function (err) {
        // handle error here
    });