如何使用 Bluebird Promises 进行分页?

7

我有类似以下代码:

new Promise (resolve, reject) ->
  trader.getTrades limit, skip, (err, trades) ->
    return reject err if err

    resolve trades
.each (trade) ->
  doStuff trade
limit被设置为某个任意数字,比如10,而skip0开始。我想不断增加skip直到没有更多的tradesdoStuff是我用来处理每个交易的函数。
这在第一次运行时有效,但我想以分页方式获取更多的交易。具体来说,我想使用更高的skip运行trader.getTrades,直到trades.length为0。

你实际上是否在使用 Promise,还是 .each doStuff 的副作用影响了你的所有代码? - Bergi
trader.getTrades 不是一个真正的函数。可以将其理解为从数据存储中返回交易的一种方式。 - Shamoon
另外,你知道第一次响应中总共有多少项吗?或者你想使用更高的“skip”值调用,直到“trades.length === 0”? - aarosil
好的。那么对于你来说,“分页式”的意思是什么呢?如果您告诉我们您想要什么,我可以告诉您如何做,否则回答您的问题就是“是” :-) - Bergi
我必须使用skip继续执行直到trades.length === 0 - Shamoon
显示剩余3条评论
3个回答

9
你应该能够使用 promisify()/promisifyAll()trader.getTrades() 转换为返回 promise 的异步版本。然后,类似下面这样的代码就可以很好地工作:
function getAllTrades(limit, offset, query) {

    var allTrades = [];

    function getTrades(limit, offset, query){
        return trader.getTradesAsync(limit, offset, query)
            .each(function(trade) {
                allTrades.push(trade)
                // or, doStuff(trade), etc.
            })
            .then(function(trades) {
                if (trades.length === limit) {
                    offset += limit;
                    return getTrades(limit, offset, query);
                } else {
                    return allTrades;
                }
            })
            .catch(function(e) {
                console.log(e.stack);
            })
    }

    return getTrades(limit, offset, query)
}

如果您提前知道总交易次数,您可以使用不同的策略与.map{concurrency: N}一起使用,以一次获取N个交易页面。


我喜欢 allTrades 和闭包的巧妙技巧,但你可以通过将旧结果与新结果连接起来更优雅地解决这个问题。 - Benjamin Gruenbaum
嗯,确实如此,然而随着HFT的普及和一切的发展,我想这些将是巨大的数组,最好保险起见。 - aarosil

8
首先,让我们隐藏那个丑陋的回调API:
var getTrades = Promise.promisify(trader.getTrades, trader);

现在,为了遍历分页API,我们将使用简单的递归下降方法:
function getAllTrades(limit, arr) {
    if (!arr) arr=[];
    return getTrades(limit, arr.length).then(function(results) {
         if (!results.length)
             return arr;
         else
             return getAllTrades(limit, arr.concat(results));
    });
}

诚然,concat并不是超级快的,因为它在每个请求之后都会创建一个新数组,但这是最优雅的方法。

此函数将返回一个 Promise,当所有请求完成后,该 Promise 将解析为一个包含所有结果的巨大数组。当然,这可能不是您想要的-也许您想立即显示第一批结果,并仅在需要时懒加载后续结果?那么单个 Promise 并不是您想要的工具,因为这种行为更像流式。不过,它仍然可以使用 Promise 编写:

getTradeChunks = (limit, start = 0) ->
  getTrades limit, start
  .then (chunk) ->
    throw new Error("end of stream") if not chunk.length
    s = start+chunk.length
    [chunk, -> getTradeChunks limit, s]

rec = ([chunk, cont]) ->
  Promise.each chunk, doStuff
  .then -> waitForClick $ "#more"
  .then cont
  .then rec, err
end = (err) ->
  $ "#more"
  .text "no more trades"
getTradeChunks 15
.then rec, err

最干净的?现在我想知道 - 如果只有一种聚合方法:) 我认为这可能是支持通用可迭代的用例。 - Benjamin Gruenbaum
@BenjaminGruenbaum:你是指集合方法吗?我不明白它们在这里怎么用。 - Bergi
哇,酷!这个也可以用纯JS实现吗?我现在使用ps.map来做类似的事情,但是想要不涉及stream概念的选项。 - aarosil
有什么方法可以避免使用巨大的数组?因为我很可能会得到一个超出机器极限的数组。 - Shamoon
1
@Shamoon 当然你可以省略数组,只传递它的长度。但是如果结果会破坏你的机器,为什么要遍历整个数组呢?这就是我在原始评论中询问你使用情况的原因。 - Bergi
显示剩余3条评论

1
这是我自己解决承诺分页的方法:page 方法,作为 spex 库的一部分。
它还可以让您根据需要进行处理节流和负载平衡。

示例

var promise = require('bluebird');
var spex = require('spex')(promise);

function source(index, data, delay) {
    // create and return an array/page of mixed values
    // dynamically, based on the index of the sequence;
    switch (index) {
        case 0:
            return [0, 1, promise.resolve(2)];
        case 1:
            return [3, 4, promise.resolve(5)];
        case 2:
            return [6, 7, promise.resolve(8)];
    }
    // returning nothing/undefined indicates the end of the sequence;
    // throwing an error will result in a reject;
}

function dest(idx, data, delay) {
    // data - resolved array data;
    console.log("LOG:", idx, data, delay);
}

spex.page(source, dest)
    .then(function (data) {
        console.log("DATA:", data); // print result;
    });

输出

LOG: 0 [ 0, 1, 2 ] undefined
LOG: 1 [ 3, 4, 5 ] 3
LOG: 2 [ 6, 7, 8 ] 0
DATA: { pages: 3, total: 9, duration: 7 }

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