承诺按顺序运行嵌套的promises并在第一次拒绝时解析

时间:2016-12-05 22:11:29

标签: javascript jquery ajax promise

我正在努力在以下任务中发送多个AJAX调用。 API返回有两个参数:userIdoffsetValue,并从指定的偏移量开始返回指定用户的最后10条消息。如果偏移量大于用户的消息总数,则API返回空字符串。

我编写了一个函数,该函数返回一个单独的承诺,以获取指定userIdoffsetValue的10条消息。

function getMessages(userId, offsetValue) {
    return new Promise(function (resolve, reject) {
        $.ajax(
        {
            url: 'https://example.com/api.php',
            type: 'POST',
            data: {
                action: 'get_messages',
                offset: offsetValue,
                user: userId
            },
            success: function (response) {
                if (response != '') {
                    resolve(response);
                } else {
                    reject(response);
                }
            },
            error: function (response) {
                reject(response);
            }
        });
    });
}

我需要使用.all()为多个userId运行并行任务,但我不能为每个userId运行并行子任务(每次递增offsetValue 10)事先不知道每个用户有多少消息,因此当第一个单独的承诺被拒绝时(即offsetValue超过总消息数),执行应该停止。像这样:

var messages = '';

getMessages('Alex', 0)
    .then(function(result) {
        messages += result;

        getMessages('Alex', 10);
            .then(function(result) {
                messages += result;

                getMessages('Alex', 20)
                ....
            });
    });

那么,有没有办法在迭代参数依次逐行的情况下运行序列承诺并在第一次拒绝时解析整体连接结果?

1 个答案:

答案 0 :(得分:5)

首先,当$.ajax()已经返回您可以使用的承诺时,您希望避免在promise anti-pattern中不必要地将代码包装在新承诺中。要解决这个问题,你可以改为:

// retrieves block of messages starting with offsetValue
// resolved response will be empty if there are no more messages
function getMessages(userId, offsetValue) {
    return $.ajax({
        url: 'https://example.com/api.php',
        type: 'POST',
        data: {
            action: 'get_messages',
            offset: offsetValue,
            user: userId
        }
    });
}

现在,针对您的主要问题。如果您想要在拒绝或空响应时停止请求新项目,并且您不知道预先会有多少请求,那么您几乎必须连续请求事项并在获得后请求下一个请求空响应或错误。 这样做的关键是通过从.then()处理程序返回新的承诺来将顺序承诺链接在一起。

你可以这样做:

function getAllMessagesForUser(userId) {
    var offsetValue = 0;
    var results = [];

    function next() {
        return getMessages(userId, offsetValue).then(function(response) {
            // if response not empty, continue getting more messages
            if (response !== '') {
                // assumes API is returning 10 results at a time
                offsetValue += 10;
                results.push(response);
                // chain next request promise onto prior promise
                return next();
            } else {
                // empty response means we're done retrieving messages
                // so just return results accumulated so far
                return results.join("");
            }
        });
    }
    return next();
}

这创建了一个返回promise的内部函数,每次获取一些消息时,它都会将新的promise链接到原始的promise上。因此,getAllMessagesForUser()返回一个单一的承诺,该承诺会解析它已检索到的所有邮件或因错误而拒绝。

您可以这样使用它:

getAllMessagesForUser('Bob').then(function(messages) {
    // got all messages here
}, function(err) {
    // error here
});

您可以并行化多个用户(只要您确定没有超载服务器或遇到速率限制问题),如下所示:

$.when.apply($, ['Bob', 'Alice', 'Ted'].map(function(item) {
    return getAllMessagesForUser(item);
})).then(function() {
    // get results into a normal array
    var results = Array.prototype.slice.call(arguments);
});

P.S。使用Promise.all()比使用$.when()更好(因为它需要一个数组并解析为一个数组),但由于这里涉及jQuery承诺,我不知道你的浏览器兼容性要求,我坚持使用jQuery承诺管理而不是ES6标准承诺。