递归链接 Promises

17

我正在制作一个简单的Windows 8应用程序,需要从网站获取一组数据。我使用WinJS.xhr()来检索这些数据,它返回一个Promise。然后,我将回调函数传递到此Promise的.then()方法中,该方法会将异步调用返回的值提供给我的回调函数。.then()方法返回另一个Promise,将回调函数返回的值赋给它。这样一个查询的基本结构如下:

WinJS.xhr({ url: "http://www.example.com/" }).then(
    function callback( result_from_xhr )
    {
        //do stuff
        return some_value;
    }).then(
    function secondcallback( some_value )
    {
        //do stuff
    });

然而,在我的情况下,根据第一个查询返回的数据,我可能需要查询更多的数据,可能还需要进行更多的查询...以此类推,递归地进行。

我需要一种编码方式,使得直到所有递归都完成之后才执行最终的.then(),与下面的代码类似:

function recurse() {
    return WinJS.xhr({ url: "http://www.example.com/" }).then(
        function callback( result_from_xhr )
        {
            if( result_from_xhr == something )
            {
               recurse();
            }
        });
}

recurse().then(
function final()
{
    //finishing code
});

问题在于,当第一层递归完成时,结束代码就被调用了。我需要一种方法,在回调函数内嵌套新承诺和旧承诺。

我希望我的问题足够清楚,我真的不确定如何解释它,异步递归代码的想法让我头疼。

4个回答

14

在这里,我会创建一个全新的、独立的promise,在recurse()函数中手动完成它,并从那里返回。然后,当你确定已完成异步工作时,完成该promise。

当你有一组已知的promises时,Promise.join是有效的 - 在调用join之前,需要整个promise数组可用。如果我按照原始问题进行操作,你可能会有一个可变数量的promises,更多的promises可能会随着异步工作的一部分而出现。在这种情况下,Join不是合适的工具。

那么,这是什么样子呢?类似于这样:

function doSomethingAsync() {
  return new WinJS.Promise(function (resolve, reject) {
    function recurse() {
      WinJS.xhr({ url: "http://www.example.com/" })
        .then(function onResult(result_from_xhr) {
          if (result_from_xhr === something) {
            recurse();
          } else {
            // Done with processing, trigger the final promise
            resolve(whateverValue);
          },
          function onError(err) {
            // Fail everything if one of the requests fails, may not be
            // the right thing depending on your requirements
            reject(err);
          });
    }
    // Kick off the async work
    recurse();
  });
}

doSomethingAsync().then(
function final()
{
    //finishing code
});

我重新安排了递归发生的位置,这样我们就不会每次都创建一个新的Promise,因此将嵌套的recurse()函数放在外层而不是外部。


很棒,我卡在创建新的 Promise 上了 - 你最后的陈述正是我所需要的。 - Larry

5
我用了一种不同的方法解决了这个问题(我认为这是同一个问题),当我制作自己的Windows 8应用程序时。我遇到这个问题的原因是,按默认设置,对表的查询将进行分页,并且每次仅返回50个结果。因此,我创建了一种模式来获取前50个结果,然后是接下来的50个结果,以此类推,直到返回的响应结果少于50个,然后承诺得以实现。
以下代码不是真正的代码,仅供说明:
function getAllRows() {
    return new WinJS.Promise(function(resolve, reject){

        var rows = [];

        var recursivelyGetRows = function(skipRows) {
            table.skip(skipRows).read()
                .then(function(results){
                    rows = rows.concat(results);

                    if (results.length < 50) {
                        resolve(rows);
                    } else {
                        recursivelyGetRows(skipRows + 50);
                    }
                })
        }

        recursivelyGetRows(0);

    });
}

所以getAllRows()返回一个Promise,在我们获得少于50个结果的情况下解析它(这表明这是最后一页)。

根据您的用例,您可能还想在其中添加一个错误处理程序。

以防不清楚,“table”是移动服务表。


我猜你需要使用return语句才能使它正常工作,对吗? - Steve Bennett
不需要改动,可以直接使用。getAllRows() 函数返回一个 Promise,并且该 Promise 会解析所有的行。 - TKoL

1
你需要使用Promise.join().done()模式。将Promise数组传递给join(),在你的情况下,这将是一组xhr调用。当所有xhr Promises完成时,join()将仅调用done()。你将获得一个结果数组传递给done(),然后可以迭代该数组并使用新的Promise.join().done()再次开始。使用此方法时要注意的事项是,如果传递给join()的Promise中有一个失败,整个操作将被视为错误条件。
抱歉,我现在没有时间为您编写代码。如果我有机会,稍后我会尝试。但是,您应该能够将此插入到您的递归函数中并使其正常工作。

我考虑使用.join()方法,但发现(至少在我的做法中)Promises被添加到数组中的时间是可预测的,即在异步调用之后进行join()调用(因为它们是在异步调用之后添加的)。我将研究使用结果数组,并在递归函数内部使用join而不是外部使用。感谢您的建议。 - Zane Geiger
看看Mike Tautly的博客-有关Promise的一些好东西。确实帮助我更加舒适地使用。http://mtaulty.com/CommunityServer/blogs/mike_taultys_blog/archive/2012/04/11/winjs-promises-1.aspx - Jeff Brand

0

好的,我解决了我的问题;我的递归函数误解了数据,因此永远不会停止递归。谢谢你的帮助,我一定会观看那些屏幕录像,因为我仍然没有完全掌握 Promise 链接结构。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接