如何在 Promise 中使用循环

Posted

技术标签:

【中文标题】如何在 Promise 中使用循环【英文标题】:How to use loops in promises 【发布时间】:2018-06-08 12:17:42 【问题描述】:

我试图在 promise 中执行一个 for 循环但没有成功,我认为我的问题与调用 resolve 的位置有关,但我不确定

/*
* Get conversations of user
* @param user String
*/
function getConversations(user)
	return new Promise(function(resolve, reject)
		var conversations = user.Conversations
		var newConversations = []
		for(var conversation of conversations) 
			helperGetConvo(conversation.ConversID).then(function(convo)
				newConversations.push(createConversationObject(messages:[], name:convo.conversationName, users:["broulaye", "doumbia"], Id:convo.conversationID))


			).catch(function(reason) 
				console.log("failure when finding conversation 2: " + reason)
			)

		
		resolve(newConversations)



	)



function helperGetConvo(convoId) 
	return new Promise (function(resolve, reject)
		query.findConversation(convoId).then(function(convers) 

			if(convers) 
				console.log("conversation was found: " + convers)
			
			else 
				console.log("conversation was not found: " + convers)
			
			resolve(convers)
		).catch(function(reason) 
			console.log("failure when finding conversation: " + reason)
		)


	)

当我像这样执行我的代码时,getConversations 函数只返回一个空数组。但是当我像这样更改 getConversations 函数时:

function getConversations(user)
	return new Promise(function(resolve, reject)
		var conversations = user.Conversations
		var newConversations = []
		for(var conversation of conversations) 
			helperGetConvo(conversation.ConversID).then(function(convo)
				newConversations.push(createConversationObject(messages:[], name:convo.conversationName, users:["broulaye", "doumbia"], Id:convo.conversationID))
				resolve(newConversations)

			).catch(function(reason) 
				console.log("failure when finding conversation 2: " + reason)
			)

		
	)

我确实得到了一个输出,但是我相信它并没有通过整个 forloop,因为据我了解,解析工作就像一个返回语句。

请大家帮忙

【问题讨论】:

你必须等待 Promise 解决,看看 Promise.all。我还建议您多阅读一些有关承诺的内容,您的代码真的很混乱。 避免promise constructor anti-pattern 【参考方案1】:

问题在于,当您调用resolve 时,您正在解决整个承诺。 for 循环不会等待每个helperGetConvo() 调用完成,然后再继续下一个。无论这些承诺中的哪一个首先遇到then 语句,都会调用resolve,这就是你的外部承诺将解决的问题。

你可以阅读更多关于 Promise 的内容:Understanding promises in node.js。

如果您想等待一组 Promise 完成,请使用 Promise.all。它接受一个 Promise 列表,并且只有在所有 Promise 都成功完成时才会解决。

function getConversations(user) 
  return new Promise(function (resolve, reject) 
    var conversations = user.Conversations;
    var newConversations = [];
    //create a list of promises
    var promises = [];
    for (var conversation of conversations) 
      // push each promise into our array
      promises.push(
        helperGetConvo(conversation.ConversID).then(function (convo) 
          newConversations.push(createConversationObject(
            messages: [],
            name: convo.conversationName,
            users: ['broulaye', 'doumbia'],
            Id: convo.conversationID
          ));
        ).catch(function (reason) 
          console.log('failure when finding conversation 2: ' + reason);
        )
      );

    
    // wait for all promises to complete
    // when then do, resolve the newConversations variable
    // which will now have all of the conversation objects that we wanted to create
    Promise.all(promises).then(() => resolve(newConversations)).catch(reject);
  );

您也可以使用 async/await 来清理它。 Async/await 提供了一些很好的语法糖来消除对return new Promise(...) 的需要。下一个代码 sn-p 不是使用 async/await 的最佳方式,因为 for 循环将同步处理所有内容(一次一个对话)。这篇博文对我理解在迭代问题中使用 async/await 非常有帮助:https://blog.lavrton.com/javascript-loops-how-to-handle-async-await-6252dd3c795。

async function getConversations(user) 
    var conversations = user.Conversations;
    var newConversations = [];

    // process each converstaion in sequence
    for (var conversation of conversations) 
      // instead of doing .then() we can use await
      // convo will have the result from the helperGetConvo
      // we put it in a try/catch because  output
      // we still want to have the error if something fails
      try 
        var convo = await helperGetConvo(conversation.ConversID);
        newConversations.push(createConversationObject(
          messages: [],
          name: convo.conversationName,
          users: ['broulaye', 'doumbia'],
          Id: convo.conversationID
        ));
       catch(reason) 
        console.log('failure when finding conversation 2: ' + reason);
      
    

  // return
  return newConversations;

异步函数返回承诺。所以你可以通过getConversations(user).then(...)来调用这个函数。但我认为 async/await 让你的代码看起来更干净。您肯定可以进行进一步的优化,但希望这可以帮助您入门。

【讨论】:

我认为你在创造不必要的承诺。 bluebirdjs.com/docs/… 可能。我试图尽可能多地保留 OP 的原始代码,并展示他如何在该结构中利用Promise.all。如果可以选择,我会将这一切更改为 async/await 以清理它。 @EricGuan 是的,我可能是,我不太熟悉这就是为什么。 @TheF1rstPancake 我不熟悉 async/await 你将如何使用它来清理它?我尝试使用它一次,一切都崩溃了,所以我切换回这个【参考方案2】:

您可以在我试图解决类似问题时发现的辅助函数中循环一个 Promise。我使用这种方法来循环 Promise,因为它不会在第一个被拒绝的 Promise 时摔倒。相反,我可以处理解析或拒绝,并在循环完成后返回最终结果。下面代码sn-p中的Promise使用的是bluebird,http://bluebirdjs.com/docs/getting-started.html

    function promiseWhile(condition, action) 
        return new Promise((resolve, reject) => 

            var loop = () => 
                if (!condition()) return resolve();
                return Promise.cast(action())
                    .then(loop)
                    .catch(reject);
            ;

            process.nextTick(loop);
            return resolve;
        )
    

我修改了您提供的一些虚拟数据的代码示例,并使其与辅助函数一起使用。因此,我相信您的 getConversations 函数将如下所示:

    function getConversations(user) 
        var conversations = user.Conversations;
        var newConversations = [];

        var stop = conversations.length;
        var index = 0

        //loop promise
        return promiseWhile(() => 
            // Condition for stopping
            return index < stop;
        , () => 
            // Action to run, should return a promise
            return new Promise((resolve, reject) => 
                helperGetConvo(conversations[index].ConversID)
                    .then(function(convo) 
                            newConversations.push(createConversationObject(
                            messages: [],
                            name: convo.conversationName,
                            users: ['broulaye', 'doumbia'],
                            Id: convo.conversationID
                            ));
                            index++;
                            resolve();
                        )
                        .catch((error) => 
                           console.log('failure when finding conversation: ' + error);
                           index++;
                           resolve();
                        );
            )
        )
            //This will execute when loop ends
            .then(() => 
                return newConversations;
            );
    

希望这会有所帮助。

【讨论】:

【参考方案3】:

你需要使用Promise.all

function getConversations(user)
    var conversations = user.Conversations
    var promises = conversations.map(c=>helperGetConvo(c.ConversID))

    return Promise.all(promises)
        .then(data=>
            let newConversations = data.map(convo=>
                return createConversationObject(messages:[], name:convo.conversationName, users:["broulaye", "doumbia"], Id:convo.conversationID)
            )
            return newConversations
        )
        .catch(reason=>
            console.log("failure when finding conversation: " + reason)
        )

像这样使用函数

getConversations(user).then(newConversations=>
    //your code
)

【讨论】:

【参考方案4】:

一种方法是使用 map 而不是 for-in 在数组中收集 Promise。然后使用 Promise.all() 等待它们全部解决(或一个被拒绝)。

类似:

return Promise.all(conversations.map(conversation => 
    return helperGetConvo(...).then().catch();

请记住,所有承诺都必须解决或拒绝。如果你不遵守这条规则,你就会遇到麻烦。

【讨论】:

以上是关于如何在 Promise 中使用循环的主要内容,如果未能解决你的问题,请参考以下文章

如何在循环中链接 Swift 中的 Promise?

如何在 forEach 循环节点中使用 Promise?

如何使用 Promise 填充对象?

每个循环中的有序 JavaScript Promise

如何将链式 Promise 与数组的 for 循环一起使用?

在 forEach 循环中等待 promise