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

Posted

技术标签:

【中文标题】承诺按顺序运行嵌套承诺并在第一次拒绝时解决【英文标题】:Promise to run nested promises sequentially and resolve upon first reject 【发布时间】:2017-04-20 10:30:21 【问题描述】:

我正在努力在以下任务中发送多个 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)
                ....
            );
    );

那么,有什么方法可以逐个逐个运行带有迭代参数的连续承诺,并在第一次拒绝时解决整体串联结果?

【问题讨论】:

你在寻找递归。 你的意思是顺序。 Promise 永远不会同步。 【参考方案1】:

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

// 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() 处理程序返回一个新的 Promise 将顺序 Promise 链接在一起。

你可以这样做:

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() 返回一个单一的 Promise,它用它检索到的所有消息进行解析,或者以错误拒绝。

你会这样使用它:

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);
);

附: Promise.all()$.when() 好用得多(因为它需要一个数组并解析为一个数组),但是由于这里已经涉及到 jQuery 承诺并且我不知道您的浏览器兼容性要求,所以我坚持使用 jQuery 承诺管理而不是 ES6 标准承诺。

【讨论】:

+1。如需切换到标准承诺,请参阅here。

以上是关于承诺按顺序运行嵌套承诺并在第一次拒绝时解决的主要内容,如果未能解决你的问题,请参考以下文章

拒绝承诺时如何解决 UnhandledPromiseRejectionWarning

扩展 Javascript 承诺并在构造函数中解决或拒绝它

嵌套承诺和拒绝

node.js 中的嵌套承诺是不是正常?

将 RN 项目从 0.46.4 升级到 0.60.0 并在 VSCode 中运行 结果无法从任何承诺中获得履行价值,所有承诺都被拒绝

处理多个承诺拒绝[重复]