2

我需要继续调用远程 API,直到我得到我需要的响应,并且我想使用 node.js 中的官方 A+ 承诺。同步伪代码:

params = { remote api call params }
while (true) {
    result = callRemoteApi(params)
    if isGood(result) {
        onSuccess(result)
        break
    }
    params = modify(params)
}

我正在使用request-promise库来处理请求,所以结果可能是这样的:

new Promise(function (resolve, reject) {
    var task = request({params})
        .then(function (result) {
            if (isGood(result)) {
                resolve(result);
            } else {
                task = request({new params}).then(this_function);
            }
        });

PS 这与https://stackoverflow.com/a/17238793/177275非常相似,但我想要一个非基于 q 的实现。

4

2 回答 2

6

像这样的东西应该很好用:

var remoteApiCall = function(num){
    // fake function to resolve promise if random number in range
    return new Promise(function(resolve, reject){
        return ((Math.random()*10) < num)
            ? resolve(true)
            : reject(false);
    })
}

function getUntil(num){
    return remoteApiCall(num).then(function(result){
        if (result) {
            return result  
        } else {
            // call again until you get valid response
            return getUntil(num)
        }
    })
}

getUntil(num).then(...)
于 2015-02-18T07:47:41.510 回答
2

以下解决方案解决了一些具体问题:

  • 如何打破(否则无休止的)循环
  • 如何访问先前失败尝试的结果
  • 如何将您的params = modify(params)

问题可以分为两个部分:

  • 一个中继器/承诺运行器(它本身返回一个承诺,所以我们可以挂钩onSuccess它)
  • 承诺提供者 - 为转发器创建承诺的功能

中继器看起来像这样:

function repeatUntilSuccess(promiseProvider) {
    return new Promise(function (resolve, reject) {
        var counter = 0;
        function run(failedResult) {
            var p = promiseProvider(failedResult, counter++);
            if (p instanceof Promise) {
                p.then(resolve).catch(run);
            } else {
                reject(p);
            }
        }
        run();
    });
}

“循环”发生在这一行:p.then(resolve).catch(run);. 中继器不断调用承诺提供者,直到它返回的承诺解决(在这种情况下中继器解决)直到它不再提供承诺(在这种情况下中继器拒绝)。

承诺提供者可以是任何function(previousResult, counter)返回承诺的提供者(如果您希望停止循环,也可以不返回)。

var params = {/* ... */};
function nextRequest(previousResult, counter) {
    if (counter >= 10) return "too many attempts";
    if (previousResult) params = modify(params);
    return apiRequest(params);
}

(此设置假定apiRequest()返回一个承诺)

现在你可以这样做:

repeatUntilSuccess(nextRequest).then(onSuccess).catch(onError);

由于您的问题包括在承诺中包装 HTTP 请求的附带任务:

function apiRequest(params) {
    return new Promise(function (resolve, reject) {
        return request(params).then(function (result) {
            if (isGood(result)) {
                resolve(result);
            } else {
                reject(result);
            }
       });
    });
}

打开浏览器控制台并运行以下代码片段以查看它的运行情况。

function repeatUntilSuccess(promiseProvider) {
    return new Promise(function (resolve, reject) {
        var counter = 0;
        function run(failedResult) {
            var p = promiseProvider(failedResult, counter++);
            if (p instanceof Promise) {
                p.then(resolve).catch(run);
            } else {
                reject(p);
            }
        }
        run();
    });
}

// mockup promise that resoves or rejects randomly after a timeout
function randomPromise(num){
    return new Promise(function(resolve, reject){
        setTimeout(function () {
            var randomNum = Math.floor(Math.random() * num * 10);
            if (randomNum < num) {
                resolve(randomNum);
            } else {
                reject(randomNum);
            }
        }, 300);
    });
}

// promise provider
function nextPromise(prev, i) {
    if (prev) console.info("failed attempt #" + i + ": " + prev);
    if (i >= 5) return "too many attempts:" + i;
    return randomPromise(100);
}

// run it!
repeatUntilSuccess(nextPromise).then(function (result) {
    console.log("success", result);
}).catch(function (result) {
    console.log("failed", result);
});

于 2015-02-18T19:55:48.637 回答