等待一个事件来解决一个承诺。

9

我正在使用一个没有回调函数的node.js模块方法。相反,它会在该方法完成时触发一个事件。我想要将该事件作为回调来解决一个promise,以确保该方法已经成功完成。

承诺中的数组长度可能是X。因此,我需要'听到' X 次事件,以确保所有方法都已成功完成 <-- 这不是问题,我只是告诉你我知道这可能会发生

事件:

tf2.on('craftingComplete', function(recipe, itemsGained){
  if(recipe == -1){
  console.log('CRAFT FAILED')
  }
  else{
        countOfCraft++;
    console.log('Craft completed! Got a new Item  #'+itemsGained);
  }
})

Promise:

const craftWepsByClass = function(array, heroClass){
        return new Promise(function (resolve, reject){

            if(array.length < 2){
                console.log('Done crafting weps of '+heroClass);
                return resolve();
            }
            else{
                for (var i = 0; i < array.length; i+=2) {
                    tf2.craft([array[i].id, array[i+1].id]); // <--- this is the module method witouth callback
                }
        return resolve(); // <---- I want resolve this, when all tf2.craft() has been completed. I need 'hear' event many times as array.length
            }   

        })
}

@guest271314 不要这样做。tf2.craft() 不返回任何东西。 - Tomas Gonzalez
具有没有回调函数的方法。好吧,on 就是一个回调函数.. :) - Keith
@Keith 但这与单个方法调用是分开的,这是一个主要问题。 - Bergi
for循环与craftingComplete事件的分派有什么关联? - guest271314
1
我知道我的英语很糟糕。我会尽力的。我需要检查事件“craftingComplete”是否已经多次触发,就像我调用tf2.craft一样。无论可能的ID或制作是否失败都没有关系。我只需要知道tf2.craft是否已经完成,并且只有为什么正在检查“craftingComplete”事件。 - Tomas Gonzalez
显示剩余10条评论
4个回答

8
首先,让我们将制作过程转换为 Promise:
function craft(elem){
 //do whatever
 return Promise((resolve,reject) => 
  tf2.on('craftingComplete', (recipe,itemsGained) => 
   if( recipe !== -1 ){
     resolve(recipe, itemsGained);
   }else{
    reject("unsuccessful");
   }
  })
);
}

因此,如果要创建多个实例,我们将数组映射到promises并使用Promise.all:

Promise.all( array.map( craft ) )
 .then(_=>"all done!")

resolvereject从哪里来的?它们似乎不在作用域内。 - Bergi
1
@Keith 原始答案的代码中没有包含 Promise 执行函数。 - guest271314
1
@Keith - 这个答案的原始版本没有在任何作用域内显示resolve()的定义。该答案已经被编辑过了。您可以查看版本历史记录,查看原始答案,并查看Bergi的回复是在最近的编辑之前显示在promise执行函数内部的。 - jfriend00
1
@Jonasw 使用 reject() 是否对应于问题中的模式?OP 是期望一个被拒绝、未处理的 Promise,还是在 countOfCraft++ 后进行后置递增操作? - guest271314
1
我认为你不想在每次调用craftWepsByClass时添加一个新的craftingComplete处理程序 - 并且永远不会删除它们。 - Bergi
显示剩余3条评论

1
如果事件将按照引起它们的相应craft()调用的顺序触发,那么您可以使用一个队列:
var queue = []; // for the tf2 instance
function getNextTf2Event() {
  return new Promise(resolve => {
    queue.push(resolve);
  });
}
tf2.on('craftingComplete', function(recipe, itemsGained) {
  var resolve = queue.shift();
  if (recipe == -1) {
    resolve(Promise.reject(new Error('CRAFT FAILED')));
  } else {
    resolve(itemsGained);
  }
});

function craftWepsByClass(array, heroClass) {
  var promises = [];
  for (var i = 1; i < array.length; i += 2) {
    promises.push(getNextTf2Event().then(itemsGained => {
      console.log('Craft completed! Got a new Item  #'+itemsGained);
      // return itemsGained;
    }));
    tf2.craft([array[i-1].id, array[i].id]);
  }
  return Promise.all(promises).then(allItemsGained => {
    console.log('Done crafting weps of '+heroClass);
    return …;
  });
}

如果您对事件的顺序一无所知,并且可能会有多个并发调用 craftWepsByClass,则无法避免全局计数器(即与 tf2 实例相关联的计数器)。缺点是,在两个重叠调用 a = craftWepsByClass(…),b = craftWepsByClass() 中,a 承诺只有在第二个调用的所有制作完成后才能解决。
var waiting = []; // for the tf2 instance
var runningCraftings = 0;
tf2.on('craftingComplete', function(recipe, itemsGained) {
  if (--runningCraftings == 0) {
    for (var resolve of waiting) {
      resolve();
    }
    waiting.length = 0;
  }
  if (recipe == -1) {
    console.log('CRAFT FAILED')
  } else {
    console.log('Craft completed! Got a new Item  #'+itemsGained);
  }
});

function craftWepsByClass(array, heroClass) {
  for (var i = 1; i < array.length; i += 2) {
    runningCraftings++;
    tf2.craft([array[i-1].id, array[i].id]);
  }
  return (runningCraftings == 0
    ? Promise.resolve()
    : new Promise(resolve => {
        waiting.push(resolve);
      })
  ).then(() => {
    console.log('Done crafting weps of '+heroClass);
  });
}

当然,在这两种解决方案中,您必须确保每次调用craft()都只会引发一个事件。

好的,抓住了,本不该有任何内容。 - Bergi

0
你可以查看event-as-promise包。它将事件连续转换为Promise,直到你完成所有的事件处理。
当与async/await结合使用时,你可以轻松地使用事件编写for循环或while循环。例如,我们想要处理data事件,直到它返回null
const eventAsPromise = new EventAsPromise();

emitter.on('data', eventAsPromise.eventListener);

let done;

while (!done) {
  const result = await eventAsPromise.upcoming();

  // Some code to process the event result
  process(result);

  // Mark done when no more results
  done = !result;
}

emitter.removeListener('data', eventAsPromise.eventListener);

如果您熟练掌握生成器函数,使用它可能会更简单。

const eventAsPromise = new EventAsPromise();

emitter.on('data', eventAsPromise.eventListener);

for (let promise of eventAsPromise) {
  const result = await promise;

  // Some code to process the event result
  process(result);

  // Stop when no more results
  if (!result) {
    break;
  }
}

emitter.removeListener('data', eventAsPromise.eventListener);

-1
我需要检查事件“craftingComplete”是否已经多次触发,因为我调用了tf2.craft。无论可能的ID或制作是否失败都无所谓。我需要知道tf2.craft是否已完成,只有为什么正在检查“craftingComplete”事件。
鉴于我们知道for循环中的i将增加i += 2,其中i小于数组的.length,我们可以在for循环之前创建一个等于该数字的变量,并将i与事件处理程序中的数字进行比较。
const craftWepsByClass = function(array, heroClass) {
  return new Promise(function(resolve, reject) {

    var countCraft = 0;
    var j = 0;
    var n = 0;
    for (; n < array.length; n += 2);

    tf2.on('craftingComplete', function(recipe, itemsGained) {
      if (recipe == -1) {
        console.log('CRAFT FAILED')
      } else {
        countOfCraft++;
        console.log('Craft completed! Got a new Item  #' + itemsGained);
        if (j === n) {
          resolve(["complete", craftCount])
        }
      }
    })

    if (array.length < 2) {
      console.log('Done crafting weps of ' + heroClass);
      return resolve();
    } else {
      try {
        for (var i = 0; i < array.length; i += 2, j += 2) {
          tf2.craft([array[i].id, array[i + 1].id]);
        }
      } catch (err) {
        console.error("catch", err);
        throw err
      }
    }

  })
}

craftWepsByClass(array, heroClass)
.then(function(data) {
  console.log(data[0], data[1])
})
.catch(function(err) {
  console.error(".catch", err)
})

@Bergi 可能不需要。craftWepsByClass() 每个新的 tf2 实例只会被调用一次。现在,OP 有几种可能的解决方案可供选择。 - guest271314
“*craftWepsByClass()”每个新的“tf2”实例中只会被调用一次。你怎么知道呢?为什么认为有多个“tf2*”实例? - Bergi
这就是整个问题所在:它可能发生,因此您必须为此设计函数。如果您做出(不常见但可想象的)假设craftWepsByClass最多只被调用一次,则应在代码中明确表示断言,或至少在文档中添加注释或通知。我在您的帖子中没有看到任何一个。 - Bergi
@Bergi 对于当前问题的任何答案都是基于可能的角度对 OP 代码实际执行情况的纯猜测。这是不言自明的。其中一种或多种方法应该有助于 OP 的视角。我们需要能够重现代码,以确保它符合 OP 的代码。我们可以“模拟”代码,但那只是更多的猜测。 - guest271314
是的。但是,如果答案没有说明做出了哪些推测,那么这个答案就不够好。我并不是批评你的观点,而是批评没有明确地陈述它——因为从不同的角度来看,代码可能是错误的,而且你的代码需要做出某些假设才能正常工作,这一点并不是显而易见的。 - Bergi
显示剩余11条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接