javascript - 带有标志变量的递归和异步方法

标签 javascript jquery ajax asynchronous recursion

我有以下方法:

function getRelevantArticles(amount, 
                             userSuggestions, 
                             suggestionPage, 
                             relevantArticles, 
                             continueFlag, 
                             success, 
                             error)
{
     if(continueFlag)
     {
         getSuggestedArticles(suggestionPage, userSuggestions, function (articles)
         {
              if(articles.length == 0)
                    getRelevantArticles(amount, userSuggestions, suggestionPage, relevantArticles, false, success, error); // continueFlag= false

              getUnvisitedArticles(articles, function (unvisited)
              {
                  for(var i = 0; i < unvisited.length; i++)
                      relevantArticles.push(unvisited[i]);

                  relevantArticles= filterRelevant(amount, userSuggestions, relevantArticles);

                  if(relevantArticles.length < amount)
                      getRelevantArticles(amount, userSuggestions, suggestionPage + 1, relevantArticles, true, success, error); // continueFlag= true
                  else
                      getRelevantArticles(amount, userSuggestions, suggestionPage, relevantArticles, false, success, error); // continueFlag= false

              }, error);
         }, error);
     } 
     else if(success)
     { 
         fillWithContent(relevantArticles, success, error); //Should be last method to execute
     }

}




上下文

我知道可能很难理解并且可以对其进行很多优化,我将尽力解释它的作用(或试图做到):

首先使用标志continueFlag= true调用该方法,因此首先调用getSuggestedArticles,这是一个发出AJAX请求的异步方法。我将请求结果传递给回调函数。

getSuggestedArticles为我提供了与用户建议有关的文章ID。 (用户建议是用户可能感兴趣的主题列表)。

我输入suggestionPage是因为建议可能很多,而且我应该只有几篇(第一页)就能获得相关的文章。

如果未检索到任何文章,则表明我们没有建议(每个建议至少都有一篇文章),即。例如,我们到达最后一页,因此我们将continueFlag标志设置为false,以调用finalizer方法。

如果至少有一篇文章,我将调用getUnvisitedArticles,这是另一个发出AJAX请求的异步方法。这种方法为我提供了用户没有访问或阅读的文章,而这些正是我所关心的。

我有一个relevantArticles变量,该变量跟踪我发现相关的文章并将呈现给用户。从当前未访问文章的页面中获取相关文章并将其附加到上一页的相关文章之后,我将检查是否有显示的文章最低数量amount

如果我还不满足最低要求,那么我继续下一页(suggestionPage + 1);

如果达到最小阈值,则继续使用终结器方法(continueFlag= false

fillWithContent是当我完成识别相关文章时将调用的方法。这是一个异步方法,它将发出AJAX请求,并用其他信息填充我的article对象。

getSuggestedArticles(编号:建议页面,数组:userSuggestions,功能:成功,功能:错误)

接收用户建议数组,并获取此数组的第n页(页面大小100)。

假设我们这样调用方法:

getSuggestedArticles(0, [ 745, 4567, 1500 ], function (data) {
      var articles = data;
}, error);


该方法向数据库Web API发出请求,并将建议的文章数组传递给success函数。在前面的示例中,articles变量将具有这样的数组(请注意,所有返回的建议文章在其主题中至少有一个用户建议):

[
    {
         id: 12345,
         topics: [ 998, 1500, 323 ] //has user suggestion 1500
    },
    {
         id: 45778,
         topics: [ 009, 1500, 745] //Has user suggestion 745 and 1500
    },
    ...
]


getUnvisitedArticles(数组:文章,功能:成功,功能:错误)

接收一系列文章,并返回所有用户未访问过的文章。

假设我们这样调用此方法:

//We are using the same "articles" variable from the previous example
getUnvisitedArticles(articles, function (data) {
     var unvisited = data;
}, error); 


该函数向Database Web Api发出请求,并将包含未访问文章的数组传递给success函数。在前面的示例中,变量unvisited将具有如下数组:

[
      {
           id: 45778,
           topics: [ 009, 1500, 745]
      }
]


请注意,编号为12345的文章已消失。这是因为用户已经访问过它。

fillWithContent(Array:relatedArticles,Function:success,Function:error)

接收一系列文章,并使用其他信息填充这些对象。

假设我们这样调用此方法:

 //We are using the same "unvisited" variable from the previous example
 fillWithContent(unvisited, function (data) {
     filledArticles = data;
 }, error);


该函数向Database Web Api发出请求,并将带有填充文章的数组传递给success函数。在前面的示例中,变量filledArticles将具有如下数组:

 [
       {
           id: 45778,
           topics: [009, 1500, 745],
           title: 'Article title',
           publicationDate: 'Some date',
           author: 'Some author',
           ...
       }
 ]


这是我的调用者正在使用的数组,调用者是指调用我的getRelevantArticles函数的那个​​数组。



问题

此方法的问题是fillWithContent被无限调用,因此导致发出大量请求,浏览器崩溃,并出现递归溢出。

我不是从另一个地方调用此方法的,因此此函数必须存在问题。

我写了一个console.log(suggestionPage),看来它也不断地无限增加变量。它应该已经在页面3处停止了,因为articles.length == 0。但这并没有停止。

这里发生了什么?

最佳答案

我认为您应该将工作负载分为独立的部分,这些部分很容易解释,并且可以轻松组合以得到更复杂的结果。

据我了解,这项工作包括三个基本部分,全部通过Ajax完成:


[主题ID]到[这些主题的文章存根](getSuggestedArticles)
[文章存根]到[相关(未读)文章存根](getUnvisitedArticles)
[文章存根]到[全文](fillWithContent)


所有这些Ajax请求都应以分页方式进行,例如1000个项目由10个请求处理,每个请求100个项目。

为此,我们定义了一个实用程序函数,该函数接受项目列表,对它们进行分页,对每个页面进行Ajax请求(通过我们作为参数传递的辅助函数)并返回所有请求的组合结果。

// utility: runs an ajax request and handles errors on the low level
function ajax(options) {
    return $.ajax(options).fail(function(jqXHR, textStatus, errorThrown) {
        console.log(textStatus, errorThrown, jqXHR);
    });
}

// utility: makes paged Ajax requests through a worker function
function pagedAjax(ajaxFunc, items, pageSize) {
    var temp = [].slice.call(items), page,
        requests = [];

    // start as many parallel Ajax requests as we have pages
    while (temp.length) {
        page = temp.splice(0, pageSize);
        requests.push( ajaxFunc(page) );
    }

    // wait until all requests have finished, return combined result
    return $.when.apply($, requests).then(function (results) {
        var combined = [];
        $.each(results, function (i, result) {
            // result is an array [data, textStatus, jqXhr]
            // push all contained items onto combined
            // (the following assumes that data is an array of objects)
            [].push.apply(combined, result[0]);
        });
        return combined;
    });
}


现在,我们可以设置三个工作程序功能。它们接受任意数量的输入,因为所有分页都由上述实用程序功能完成:

// worker: retrieves a list of article IDs from topic IDs
function getSuggestedArticles(topics) {
    return ajax({method: 'post', url: '/articlesByTopic', data: topics});
    // or whatever API request returns a list of articles IDs from topic IDs
}

// worker: takes a list of article IDs, returns a list of _unread_ article IDs
function getUnvisitedArticles(articles) {
    return ajax({method: 'post', url: '/unvisitedArticles', data: articles});
    // or whatever API request returns a list of unvisited articles from IDs
}

// worker: takes a list of article IDs, returns a list of articles
function fillWithContent(articles) {
    return ajax({method: 'post', url: '/articles', data: articles});
    // or whatever API request fills articles with content
}


之后,组合功能就不再困难了:

// takes a list of topic IDs, requests article IDs, filters them, returns actual articles
function getUnvisitedArticlesByTopic(topicIds) {
    var pageSize = 100;

    return pagedAjax(getSuggestedArticles, topicIds, pageSize)
        .then(function (allArticles) {
            return pagedAjax(getUnvisitedArticles, allArticles, pageSize);
        })
        .then(function (unvisitedArticles) {
            return pagedAjax(fillWithContent, unvisitedArticles, pageSize);
        });
}


我们可以通过一个非常简单的调用来使用它:

// renders unvisited articles 
function renderUnvisitedArticles() {
    var topicIds = [9, 1500, 745];

    getUnvisitedArticlesByTopic(topicIds).done(function (articles) {
        $.each(articles, function (i, article) {
            // show article on page
        });
    });
}


这种基于承诺的方法的好处:


没有回调地狱。
简短的功能只能做一件事。
没有自呼功能。
各个零件具有良好的可重用性和可测试性。


推荐阅读的当然是jQuery的Deferred objects文档。

免责声明:该代码确实未经测试。如果发现错误,请告诉我。

关于javascript - 带有标志变量的递归和异步方法,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/31835492/

相关文章:

javascript - Jquery 打开关闭 div 缺少一些东西

javascript - 表单提交不适用于 WordPress 管理中的下拉更改

jquery - 如何在溢出 :hidden area? 中添加 DOM 元素

javascript - 在ajax调用后 react 组件状态变化但不重新渲染组件

php - AJAX 加载页面中的 Javascript

javascript - 如何复制 SharePoint 2010 内部进行的社交 ajax 调用?

javascript - 检查用户输入是否等于数组值

javascript函数按顺序调用,由JSP制作

javascript - Eslint 未检测到 React 组件的定义属性类型

javascript - 按升序添加按钮和按降序删除按钮