How to replace Promise.defer with new Promise()

The direct equivalent using new Promise(constructorFn) would be to write interval = setInterval(...) inside constructorFn and to pass constructorFn's resolve argument to rollDice() instead of your original Deferred object.

var Promise = require('bluebird');
var interval;

var rollDice = function (resolve) {
  console.log("rolling");
  if (Math.floor(Math.random() * 10) == 7) {
    clearInterval(interval);
    resolve();
  }
};

var rollTill7 = function (ms) {
    return new Promise(function(resolve, reject) {
        interval = setInterval(function() {
            rollDice(resolve);
        }, ms);
    });
};

rollTill7(100).then(function() {
    console.log("rolled a 7");
});

The solution becomes much neater if interval and rollDice() are also moved the into the promise's constructor. Not only are two members removed from the outer namespace but also the need to pass resolve is avoided - it becomes reachable by rollDice() due to closure.

var Promise = require('bluebird');

var rollTill7 = function (ms) {
    return new Promise(function(resolve, reject) {
        var interval;
        var rollDice = function () {
          console.log("rolling");
          if (Math.floor(Math.random() * 10) == 7) {
            clearInterval(interval);
            resolve();
          }
        };
        interval = setInterval(rollDice, ms);
    });
};

rollTill7(100).then(function() {
    console.log("rolled a 7");
});

Going one tiny but arguably significant step further, you would probably choose to move rollDice() into the setInterval expression as an anonymous function. This would side-step an issue with the named function that it does more than just "roll a dice" - it also resolves.


In general, it is recommended to move away from the old defer model because you want the creator of a promise to be responsible for resolving or rejecting it - this just makes the control flow a ton easier to follow. You don't want to pass on the responsibility for resolving or rejecting to some other code.

If outside code is involved in the decision to resolve or reject (such as your rollDice() function), then it can return info which is used to resolve or reject. For example, in your code example, it can be done like this.

Note that the rollDice() function is now just a dice rolling function that tells you whether you rolled a specific number of not. It is then used by the other function to determine control flow rather than putting control flow in the dice rolling function itself.

var rollDice = function() {
  console.log("rolling");
  return Math.floor(Math.random() * 10) + 1;
}

var rollTillNum = function(num, ms) {
    return new Promise(function(resolve) {
        var interval = setInterval(function(){
            if (rollDice() === num) {
               resolve();
               clearInterval(interval);
            }
        }, ms);
    });
}

rollTillNum(7, 100).then(function(){
    console.log("rolled a 7");
});

Summary of Changes:

  1. Promise management is self-contained, not delegated to some other function (makes logic of the code easier to follow) which is one of the main reasons to use the new Promise constructor and not the deferred construct.
  2. The interval variable is now contained to a local scope.
  3. The rollDice() function is now generic so it can be used in other contexts.
  4. rollDice() now returns a 1-based value, not a 0-based value (since that's how dice work).
  5. Rather than hardwire rollTill7(), it's now rollTillNum() and you pass it the number you want it to achieve.

While the above solution is more general (using an external function to provide feedback on whether you should resolve or not), in this specific case, if you don't even need the rollDice() function to be externally usable, then it can just be subsumed entirely inside of the rollTillNum() function:

var rollTillNum = function(num, ms) {
    return new Promise(function(resolve) {
        var interval = setInterval(function(){
            if ((Math.floor(Math.random() * 10) + 1) === num) {
               resolve();
               clearInterval(interval);
            }
        }, ms);
    });
}

rollTillNum(7, 100).then(function(){
    console.log("rolled a 7");
});

Here's the above code made into a working demo:

document.getElementById("roll").addEventListener("click", function() {
    var start = Date.now();
    rollTillNum(7, 100).then(function(cnt) {
        var elapsed = ((Date.now() - start) / 1000).toFixed(1);
        log("It took " + elapsed + " seconds and " + cnt + " rolls to roll a 7");
    });
});

var rollDice = function() {
  console.log("rolling");
  return Math.floor(Math.random() * 10) + 1;
}

var rollTillNum = function(num, ms) {
    return new Promise(function(resolve) {
        var cntr = 0;
        var interval = setInterval(function(){
            ++cntr;
            if (rollDice() === num) {
               resolve(cntr);
               clearInterval(interval);
            }
        }, ms);
    });
}

function log(x) {
    var div = document.createElement("div");
    div.innerHTML = x;
    document.body.appendChild(div);
}
<button id="roll">
Roll a 7
</button><br><br>