当前位置: 首页 > 知识库问答 >
问题:

如何同步一系列promise?

施念
2023-03-14

我有一个promise对象数组,这些对象必须按照html" target="_blank">数组中列出的顺序进行解析,也就是说,在解析前一个元素之前,我们不能尝试解析元素(正如方法promise.all([…])所做的那样)。

如果一个元素被拒绝,我需要链立即拒绝,而不试图解析下面的元素。

我如何实现这一点,或者对于这样的序列模式是否存在现有的实现?

function sequence(arr) {
    return new Promise(function (resolve, reject) {
        // try resolving all elements in 'arr',
        // but strictly one after another;
    });
}

编辑

最初的答案表明,我们只能序列这些数组元素的结果,而不是它们的执行,因为它在这样的例子中是预定义的。

那么,如何以避免提前执行的方式生成一系列promise呢?

这里有一个修改过的例子:

function sequence(nextPromise) {
    // while nextPromise() creates and returns another promise,
    // continue resolving it;
}

我不想把它变成一个单独的问题,因为我相信这是同一个问题的一部分。

解决方案

下面的一些答案和随后的讨论有点误入歧途,但最终的解决方案完全符合我的要求,它是在spex库中实现的,作为方法序列。该方法可以遍历动态长度序列,并根据应用程序的业务逻辑创建promise。

后来我把它变成了一个供大家使用的共享库。

共有3个答案

相化
2023-03-14

您不能简单地运行X异步操作,然后希望按顺序解析它们。

执行类似操作的正确方法是仅在解析之前的异步操作后运行新的异步操作:

doSomethingAsync().then(function(){
   doSomethingAsync2().then(function(){
       doSomethingAsync3();
       .......
   });
});

编辑
似乎您希望等待所有promise,然后以特定的顺序调用它们的回调。像这样的东西:

var callbackArr = [];
var promiseArr = [];
promiseArr.push(doSomethingAsync());
callbackArr.push(doSomethingAsyncCallback);
promiseArr.push(doSomethingAsync1());
callbackArr.push(doSomethingAsync1Callback);
.........
promiseArr.push(doSomethingAsyncN());
callbackArr.push(doSomethingAsyncNCallback);

然后:

$.when(promiseArr).done(function(promise){
    while(callbackArr.length > 0)
    {
       callbackArr.pop()(promise);
    }
});

当一个或多个promise失败时,可能会出现这样的问题。

融烨磊
2023-03-14

promise代表运营的价值,而不是运营本身。操作已经开始,所以你不能让他们互相等待。

相反,您可以同步返回promise的函数,并按顺序调用它们(例如,通过带有promise链接的循环),或者使用bluebird中的.each方法。

明安阳
2023-03-14

下面是一些简单的示例,介绍如何通过一个数组依次执行每个异步操作。

假设您有一个项目数组:

var arr = [...];

并且,您希望对数组中的每个项执行一个特定的异步操作,一次一个地连续执行,以便在上一个操作完成之前,下一个操作不会启动。

并且,假设您有一个promise返回函数,用于处理数组中的一个项fn(item)

function processItem(item) {
    // do async operation and process the result
    // return a promise
}

然后,您可以这样做:

function processArray(array, fn) {
    var index = 0;
    
    function next() {
        if (index < array.length) {
            fn(array[index++]).then(next);
        }
    }
    next();
}

processArray(arr, processItem);

如果希望从processArray()返回promise,以便知道何时完成,可以将以下内容添加到promise中:

function processArray(array, fn) {
    var index = 0;

    function next() {
        if (index < array.length) {
            return fn(array[index++]).then(function(value) {
                // apply some logic to value
                // you have three options here:
                // 1) Call next() to continue processing the result of the array
                // 2) throw err to stop processing and result in a rejected promise being returned
                // 3) return value to stop processing and result in a resolved promise being returned
                return next();
            });
        }
    } else {
        // return whatever you want to return when all processing is done
        // this returne value will be the ersolved value of the returned promise.
        return "all done";
    }
}

processArray(arr, processItem).then(function(result) {
    // all done here
    console.log(result);
}, function(err) {
    // rejection happened
    console.log(err);
});

注意:这将在第一次拒绝时停止链,并将该原因传递回processArray返回的promise。

如果你想用promise做更多的工作,你可以将所有promise连成链:

function processArray(array, fn) {
   return array.reduce(function(p, item) {
       return p.then(function() {
          return fn(item);
       });
   }, Promise.resolve());
}

processArray(arr, processItem).then(function(result) {
    // all done here
}, function(reason) {
    // rejection happened
});

注意:这将在第一次拒绝时停止链,并将该原因传递回从processArray()返回的promise。

对于成功场景,从processArray()返回的promise将使用fn回调的上次解析值进行解析。如果您想累积一个结果列表并使用该列表进行解析,您可以从fn收集闭包数组中的结果,并继续每次返回该数组,以便最终解析为结果数组。

而且,由于现在看起来很明显,您希望最终的promise结果是一个数据数组(按顺序排列),因此这里是对先前解决方案的一个修订,它产生了:

function processArray(array, fn) {
   var results = [];
   return array.reduce(function(p, item) {
       return p.then(function() {
           return fn(item).then(function(data) {
               results.push(data);
               return results;
           });
       });
   }, Promise.resolve());
}

processArray(arr, processItem).then(function(result) {
    // all done here
    // array of data here in result
}, function(reason) {
    // rejection happened
});

工作演示:http://jsfiddle.net/jfriend00/h3zaw8u8/

还有一个显示拒绝的工作演示:http://jsfiddle.net/jfriend00/p0ffbpoc/

如果要在操作之间插入一个小延迟:

function delay(t, v) {
    return new Promise(function(resolve) {
        setTimeout(resolve.bind(null, v), t);
    });
}

function processArrayWithDelay(array, t, fn) {
   var results = [];
   return array.reduce(function(p, item) {
       return p.then(function() {
           return fn(item).then(function(data) {
               results.push(data);
               return delay(t, results);
           });
       });
   }, Promise.resolve());
}

processArray(arr, 200, processItem).then(function(result) {
    // all done here
    // array of data here in result
}, function(reason) {
    // rejection happened
});

Bluebird promise库有许多内置的并发控制特性。例如,要对数组中的迭代进行排序,可以使用Promise.mapSeries()

Promise.mapSeries(arr, function(item) {
    // process each individual item here, return a promise
    return processItem(item);
}).then(function(results) {
    // process final results here
}).catch(function(err) {
    // process array here
});

或在迭代之间插入延迟:

Promise.mapSeries(arr, function(item) {
    // process each individual item here, return a promise
    return processItem(item).delay(100);
}).then(function(results) {
    // process final results here
}).catch(function(err) {
    // process array here
});

如果你在一个支持异步/wait的环境中编码,你也可以只使用一个常规的for循环,然后wait循环中的一个promise,它会导致for循环暂停,直到一个promise在继续之前解决。这将有效地对异步操作进行排序,这样下一个操作直到前一个操作完成后才开始。

async function processArray(array, fn) {
    let results = [];
    for (let i = 0; i < array.length; i++) {
        let r = await fn(array[i]);
        results.push(r);
    }
    return results;    // will be resolved value of promise
}

// sample usage
processArray(arr, processItem).then(function(result) {
    // all done here
    // array of data here in result
}, function(reason) {
    // rejection happened
});

仅供参考,我认为我这里的processArray()函数非常类似于Bluebird Promise库中的Promise.map(),它接受一个数组和一个Promise生成函数,并返回一个Promise,该Promise使用一个解析结果数组进行解析。

@vitaly-t-这里有一些关于你的方法的更详细的评论。欢迎您使用您认为最好的代码。当我第一次开始使用promise时,我倾向于将promise仅用于他们所做的最简单的事情,并且自己编写了很多逻辑,而更高级的promise可以为我做更多的事情。您只使用您完全熟悉的代码,除此之外,您更希望看到自己熟悉的代码。这可能是人的本性。

我会建议,随着我越来越了解Promissions可以为我做什么,我现在喜欢编写使用Promissions的更多高级功能的代码,这对我来说似乎是非常自然的,我觉得我是在经过良好测试的基础设施上构建的,它有很多有用的功能。我只想请你保持思想开放,因为你知道越来越多的潜在方向。我认为,随着理解的提高,迁移是一个有用且富有成效的方向。

以下是对您的方法的一些具体反馈:

你在七个地方做出promise

作为风格上的对比,我的代码只有两个地方显式创建新promise——一次是在工厂函数中,一次是初始化。减少()循环。在其他地方,我只是通过链接到它们或者返回其中的值或者直接返回它们来构建已经创建的promise。您的代码有七个独特的地方,您在其中创建一个promise。现在,好的编码并不是一场竞赛,看你能创建promise的地方有多少,但这可能会指出利用已经创建的promise与测试条件和创建新promise的区别。

投掷安全是一个非常有用的功能

promise是安全的。这意味着promise处理程序中抛出的异常将自动拒绝该promise。如果您只是想将异常变成拒绝,那么这是一个非常有用的特性,可以加以利用。事实上,你会发现,仅仅抛开自己是一种有效的方式,可以拒绝来自处理程序内部的请求,而不需要创建另一个promise。

大量的Promise.resolve()Promise.reject()可能是一个简化的机会

如果您看到代码中有大量的Promise.resolve()Promise.reject()语句,那么可能有机会更好地利用现有的promise,而不是创建所有这些新promise。

信守promise

如果你不知道某件事是否回报了一个promise,那么你可以把它变成一个promise。然后promise库将自己检查它是否是promise,甚至它是否是与您正在使用的promise库匹配的promise类型,如果不是,则将其包装成一个promise库。这样可以避免自己重写大量的逻辑

还债契约

如今,在许多情况下,完全可以为一个函数签订合同,该函数可以执行异步操作以返回promise。如果函数只是想做一些同步的事情,那么它可以返回一个已解决的promise。你似乎觉得这很繁重,但这肯定是风吹过的方式,我已经写了很多需要这样做的代码,一旦你熟悉了promise,感觉很自然。它抽象出操作是同步的还是异步的,调用方不必知道或做任何特殊的事情。这是一个很好的promise用法。

工厂函数可以编写为仅创建一个promise

工厂函数可以编写为只创建一个promise,然后解决或拒绝它。此样式还使其抛出安全,因此factory函数中发生的任何异常都会自动成为拒绝。它还使总是返回promise的合同自动生效。

虽然我意识到这个工厂函数是一个占位符函数(它甚至不做任何异步),希望你能看到它的风格:

function factory(idx) {
    // create the promise this way gives you automatic throw-safety
    return new Promise(function(resolve, reject) {
        switch (idx) {
            case 0:
                resolve("one");
                break;
            case 1:
                resolve("two");
                break;
            case 2:
                resolve("three");
                break;
            default:
                resolve(null);
                break;
        }
    });
}

如果这些操作中的任何一个是异步的,那么他们可以返回他们自己的promise,这些promise会自动链接到一个中心promise,就像这样:

function factory(idx) {
    // create the promise this way gives you automatic throw-safety
    return new Promise(function(resolve, reject) {
        switch (idx) {
            case 0:
                resolve($.ajax(...));
            case 1:
                resole($.ajax(...));
            case 2:
                resolve("two");
                break;
            default:
                resolve(null);
                break;
        }
    });
}

使用拒绝处理程序只返回promise。不需要拒绝(原因)

当你有这样的代码体:

    return obj.then(function (data) {
        result.push(data);
        return loop(++idx, result);
    }, function (reason) {
        return promise.reject(reason);
    });

拒绝处理程序没有添加任何值。你可以这样做:

    return obj.then(function (data) {
        result.push(data);
        return loop(++idx, result);
    });

您已经返回了obj.then()的结果。。如果obj拒绝,或者如果链接到obj或从返回的任何内容被处理程序拒绝,那么obj将拒绝。所以,您不需要用拒绝创建新的promise。没有拒绝处理程序的更简单的代码用更少的代码做同样的事情。

下面是代码的一般体系结构中的一个版本,它尝试将这些想法结合起来:

function factory(idx) {
    // create the promise this way gives you automatic throw-safety
    return new Promise(function(resolve, reject) {
        switch (idx) {
            case 0:
                resolve("zero");
                break;
            case 1:
                resolve("one");
                break;
            case 2:
                resolve("two");
                break;
            default:
                // stop further processing
                resolve(null);
                break;
        }
    });
}


// Sequentially resolves dynamic promises returned by a factory;
function sequence(factory) {
    function loop(idx, result) {
        return Promise.resolve(factory(idx)).then(function(val) {
            // if resolved value is not null, then store result and keep going
            if (val !== null) {
                result.push(val);
                // return promise from next call to loop() which will automatically chain
                return loop(++idx, result);
            } else {
                // if we got null, then we're done so return results
                return result;
            }
        });
    }
    return loop(0, []);
}

sequence(factory).then(function(results) {
    log("results: ", results);
}, function(reason) {
    log("rejected: ", reason);
});

工作演示:http://jsfiddle.net/jfriend00/h3zaw8u8/

关于这一实施的一些意见:

>

工厂函数通过返回null或其解析值最终为null的promise来发出信号。上面的强制转换将这两个条件映射到相同的结果代码。

factory函数自动捕获异常并将其转换为拒绝,然后由sequence()函数自动处理。如果您只想中止处理并在第一个异常或拒绝时反馈错误,那么让promise执行大量错误处理,这是一个显著的优点。

此实现中的工厂函数可以返回一个promise或一个静态值(对于同步操作),并且它将正常工作(根据您的设计请求)。

我在工厂函数的promise回调中使用抛出的异常对其进行了测试,它确实只是拒绝并传播该异常,以拒绝序列promise,并将异常作为原因。

这使用了与您类似的方法(有意地,尝试保持您的通用体系结构)将多个调用链接到loop()

 类似资料:
  • 我有一个需要在循环中执行的请求promise,比如: 问题是我永远不知道数组中有多少元素,所以我需要一个动态模式。有没有可能混合同步和异步世界,以便在一个时刻只有一个请求是活动的(序列不重要)?

  • 问题内容: 我有一个promise对象数组,必须按数组中列出的顺序来解决它们,即,在解析前一个元素之前,我们无法尝试解析一个元素(如方法一样)。 而且,如果一个元素被拒绝,则我需要链立即拒绝,而无需尝试解决以下元素。 如何实现此功能,或者该模式是否已有实现? 编辑 最初的答案表明,我们只能得到此类数组元素的结果,而不能执行它们,因为在此示例中它是预定义的。 但是,如何以避免提前执行的方式生成承诺数

  • 我有两个线程用于在线游戏制作。一个线程接收两个 X 和 Y 数字,另一个线程将 X 和 Y 数字发送给登录到服务器的每个人。基本上,我需要一个队列,这将允许第一个线程将2D数组添加到队列中,并继续这样做,同时,将数组拉出并从队列中删除数组以用于发送给其他玩家。有没有办法做到这一点?我知道我需要一个同步的队列,但是我如何同步一个,这被称为阻塞队列吗? 总的来说,我需要一个同步的队列,这将允许两个线程

  • 我在我的Laravel项目中与雄辩的关系作斗争。 我的数据库中有一个“用户”表和一个“用户行”表。“用户行”表中有一个“用户id”字段,该字段与“用户”中的“id”字段相对应,属于典型的主-明细关系。 用户模型与用户线模型有很多关系。UserLine模型与用户模型具有belongsTo关系。 在我的应用程序中,我有一个包含多个用户行的用户表单。在表单提交期间,可以添加、更改或删除这些用户行。到目前

  • 问题内容: 我一直在使用此功能并在很长时间内都避免在错误修复列表中出现此错误,但是我终于到达了列表的末尾,最后我必须使函数返回true / false以声明是否验证成功与否。 我正在使用ajax将某些字段与数据库中已有的字段进行比较,并且默认情况下该方法以异步方式进行操作。 我在调用中设置了一个变量,因此,调用方法没有得到响应,因此我的所有js / jquery在pageLoad上都失败了…如果我

  • 本文向大家介绍java同步之如何写一个锁Lock,包括了java同步之如何写一个锁Lock的使用技巧和注意事项,需要的朋友参考一下 问题 (1)自己动手写一个锁需要哪些知识? (2)自己动手写一个锁到底有多简单? (3)自己能不能写出来一个完美的锁? 简介 本篇文章的目标一是自己动手写一个锁,这个锁的功能很简单,能进行正常的加锁、解锁操作。 本篇文章的目标二是通过自己动手写一个锁,能更好地理解后面