8

I am using a node.js module that has a method without callbacks. Instead of it, has an event that fires when that method has finished. I want resolve a promise, using that event as callback securing me that method has been completed succesfully.

array.lenght on promise can be X. So, I need 'hear' X times the event to secure me that all methods has completed succesfully <-- This is not the problem, I am just telling you that I know this could happen

Event :

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
            }   

        })
}
Tomas Gonzalez
  • 129
  • 1
  • 1
  • 9
  • Does `tf2.craft()` return a `Promise`? Note, a `Promise` can only be resolved or rejected once. – guest271314 Oct 18 '17 at 19:45
  • @guest271314 Do not. tf2.craft() return nothing; – Tomas Gonzalez Oct 18 '17 at 19:47
  • `that has a method without callbacks.` well `on` is a callback.. :) – Keith Oct 18 '17 at 19:49
  • @Keith But it's separate from the individual method invocation, and that's a major problem – Bergi Oct 18 '17 at 19:50
  • Are you passing any arguments to the method, and does the event somehow depend on that? Do the events (for each `craft` run) fire in the same order as the original calls? What is `tf2`, do you have any links to documentation or implementation? – Bergi Oct 18 '17 at 19:51
  • @Bergi I' not seeing the rest of the tf2, but I can't see why it would be a problem with promises.. – Keith Oct 18 '17 at 19:52
  • How is the `for` loop related to the `craftingComplete` event being dispatched? – guest271314 Oct 18 '17 at 19:53
  • @Keith Because you don't know which event belongs to which call, and you don't know which promise to resolve. You have to assume there are multiple concurrent calls to `craftWepsByClass`. – Bergi Oct 18 '17 at 19:54
  • How do you know that `array[i+1]` exists? Would not `array[i+1]` be `undefined` at last iteration of `for` loop, where `i < array.length` is the condition and `i+=2` the increment expression? – guest271314 Oct 18 '17 at 19:54
  • @Bergi You must know more about the tf2 class than I do, as I'm struggling to see the problem. If everything he needs in the recipe, itemsGained callback, jobs done. Is the method invocation needed later??. – Keith Oct 18 '17 at 19:57
  • @guest271314 Forget about if arra[i+1] is undefined. I could handle that. I need to know how I use that event as callback of tf2.craft(). And recipe has nothing to do here. I know it would helpful if I specify each recipe and then check event for same recipe, etc. But I cant specify each recipe – Tomas Gonzalez Oct 18 '17 at 20:00
  • Is expected result a single `Promise` value or multiple `Promise` values? – guest271314 Oct 18 '17 at 20:02
  • @Keith The problem is: *which* job is done? There might have been multiple jobs running concurrently, with multiple promises to resolve when the respective job is done - but if all jobs fire the same kind of events, they're not distinguishable. You must know more about `tf2` than I do, stating that `recipe` and `itemsGained` is everything one needs to do that. – Bergi Oct 18 '17 at 20:05
  • 1
    I know I am a terrible english speaker. I wil try my best. I need check if event 'craftingComplete' has fired many times as I call tf2.craft. Doesnt matters any posible ID or if craft has failed. I need to know if tf2.craft has finished and only why is checking 'craftingComplete' event – Tomas Gonzalez Oct 18 '17 at 20:13
  • @Bergi I know you know all this stuff, so I'll leave this one for you. I'm maybe missing the problem you say exists. For me I can't see any issues. Wrap the tf2 in a promise, even promise.all returns in order, so if say you later wanted to go and map to an array of tf2's requests. – Keith Oct 18 '17 at 20:16

4 Answers4

7

At first lets promisify the crafting:

function craft(elem){
 //do whatever
 return Promise((resolve,reject) => 
  tf2.on('craftingComplete', (recipe,itemsGained) => 
   if( recipe !== -1 ){
     resolve(recipe, itemsGained);
   }else{
    reject("unsuccessful");
   }
  })
);
}

So to craft multiples then, we map our array to promises and use Promise.all:

Promise.all( array.map( craft ) )
 .then(_=>"all done!")
Jonas Wilms
  • 132,000
  • 20
  • 149
  • 151
  • Where do `resolve` and `reject` come from? They don't appear to be in scope. – Bergi Oct 18 '17 at 19:52
  • @Bergi are you having an off day.? resolve & reject are in the Promise callback.. – Keith Oct 18 '17 at 19:58
  • 1
    @Keith The `Promise` executor function was not included at code at original Answer – guest271314 Oct 18 '17 at 20:00
  • I will try with this idea. – Tomas Gonzalez Oct 18 '17 at 20:02
  • 1
    @Keith - The original version of this answer did not show `resolve()` inside any scope that would have that defined. The answer has since been edited. You can look at the version history, see the original answer and see that Bergi's response was before the recent edit that showed it inside a promise executor function. – jfriend00 Oct 18 '17 at 20:05
  • 1
    @Jonasw Does the use of `reject()` correspond to the pattern at Question? Is OP expecting a rejected; note, unhandled; `Promise` or to post increment `countOfCraft` at `countOfCraft++`? – guest271314 Oct 18 '17 at 20:05
  • @guest271314 Yes, @Bergi's comment seemed to appear after the edit for. SO refresh thing. Apart from that if you do Promise's, it's pretty obvious were `resolve` & `reject` have come from. But I know this is Bergi's way.. :) – Keith Oct 18 '17 at 20:20
  • 1
    I don't think you want to add a new `craftingComplete` handler every time when `craftWepsByClass` is called - and never remove them. – Bergi Oct 19 '17 at 00:00
1

If the events will be fired in the same order as the respective craft() calls that caused them, you can use a queue:

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

If you don't know anything about the order of the events, and there can be multiple concurrent calls to craftWepsByClass, you cannot avoid a global counter (i.e. one linked to the tf2 instance). The downside is that e.g. in two overlapping calls a = craftWepsByClass(…), b = craftWepsByClass() the a promise won't be resolved until all crafting of the second call is completed.

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

Of course in both solutions you must be 100% certain that each call to craft() causes exactly one event.

Bergi
  • 630,263
  • 148
  • 957
  • 1,375
0

You can look at the event-as-promise package. It convert events into Promise continuously until you are done with all the event processing.

When combined with async/await, you can write for-loop or while-loop easily with events. For example, we want to process data event until it return 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);

If you are proficient with generator function, it may looks a bit simpler with this.

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);
Compulim
  • 1,148
  • 10
  • 18
-1

I need check if event 'craftingComplete' has fired many times as I call tf2.craft. Doesnt matters any posible ID or if craft has failed. I need to know if tf2.craft has finished and only why is checking 'craftingComplete' event

Given that we know i within for loop will be incremented i += 2 where i is less than .length of array, we can create a variable equal to that number before the for loop and compare i to the number within event handler

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)
})
guest271314
  • 1
  • 15
  • 104
  • 177
  • I don't think you want to add a new `craftingComplete` handler every time when `craftWepsByClass` is called. – Bergi Oct 19 '17 at 00:00
  • @Bergi Probably not. `craftWepsByClass()` would only be called one time for each new `tf2` instance. OP now has several possible solutions to select from. – guest271314 Oct 19 '17 at 00:02
  • "*`craftWepsByClass()` would only be called one time for each new `tf2` instance.*" - how do you know that? Why do you even think there are multiple `tf2` instances? – Bergi Oct 19 '17 at 00:07
  • @Bergi _"how do you know that? Why do you even think there are multiple `tf2` instances?"_ The same evaluation applies as to "every time when `craftWepsByClass` is called." – guest271314 Oct 19 '17 at 00:09
  • That's the whole problem: it *can* happen, so you have to design your function for that. If you make the (uncommon but conceivable) assumption that `craftWepsByClass` is called at most once, you should make that explicit with an assertion in the code, or at least a comment or a notice in the documentation. I'm missing any of those in your post. – Bergi Oct 19 '17 at 00:13
  • @Bergi _"That's the whole problem: it can happen"_ If it does, then adjust the code.; else use your design consideration. – guest271314 Oct 19 '17 at 00:14
  • But you made no design considerations in your answer, no mentions that it doesn't happen. So we would assume that it can happen, and your code might break. Just add a statement about the assumption to your answer to make its design valid. – Bergi Oct 19 '17 at 00:18
  • @Bergi Any Answer to the present Question is pure speculation based on possible perspectives as to what code at OP actually does. That is self evident. One or more of the approaches should add to OP's perspective. We need to be able to reproduce the code to be certain it comports with the code at OP. We could "mock" the code, though that is only more speculation. – guest271314 Oct 19 '17 at 00:23
  • Yes. But an answer that doesn't state what speculations were made is a bad answer. I'm not criticising your perspective, I'm criticising the lack of stating it explicitly - since from a different perspective, the code would be wrong, and it's not immediately obvious that your code makes certain assumptions to work. – Bergi Oct 19 '17 at 00:46
  • @Bergi The code itself states that the pattern addresses a single instance of `tf2` and the events dispatched therefore; following the pattern at Question, which attempts to resolve exactly one `Promise` object. – guest271314 Oct 19 '17 at 04:35
  • The text of the question calls `tf2` a "module", not an "instance". There's no reason to assume that there are multiple instances, or that each would be used only once - and using a module singleton only once makes even less sense. The code in the question attempts to resolve exactly one new `Promise` *per call* of `craftWepsByClass`. Please don't try to read any meaning into the non-working pattern in the question, just edit your answer. – Bergi Oct 19 '17 at 04:41
  • @Bergi Gather your point. However, the events appear to have a definitive end, that is when `tf2.craft()` is called N times. What we could do is once `.then()` is reached remove the event handler and call `craftWepsByClass()` again, if necessary, to repeat the procedure. – guest271314 Oct 19 '17 at 04:45
  • Please put that in your answer, not in a comment. – Bergi Oct 19 '17 at 04:47
  • @Bergi Not sure why that is not self-evident by the code itself following the pattern at OP? Where does code at OP expect multiple `Promise` objects to be resolved? The code appears to expect exactly one `Promise` object to be resolved. If `tf2.craft()` is not called again then `'craftingComplete'` event will not be dispatched. – guest271314 Oct 19 '17 at 04:48
  • If you want to be that picky, then the code in the question appears to expect no promise to ever be resolved since `craftWepsByClass` is never called anywhere in the question. No, the supposed usage pattern of the function is *not* self-evident. – Bergi Oct 19 '17 at 05:04
  • @Bergi Well, that is a valid point, the function is not called at Question, though that fact is not related to code at Answer, where the function is called. A single `Promise` constructor is included at code at Answer, as a single `Promise` constructor appears at code at Question. Perhaps OP will chime in with feedback as to the various possible approaches thus far posted as answers. – guest271314 Oct 19 '17 at 05:07