2

JavaScript/Promise experts,

I hope you can help me, because I don't understand how I can create a function that returns an existing promise instead of a new promise. I have done a lot of research, but all examples return a new promise every time the function is called.

Assume I have a function that takes some time to process and generates a different result every time it is being called. When I use the new promise method then I still need to wait to complete every time and I don't get the same value if I recall the function.

I have included proof of concept I have created that works as desired (unless I have overlooked something). I think this "solution" is ugly, because I need to move variables to a higher scope and I still need to create a new promise every time the function is called.

My assumption at this moment; to make this nice code the function needs to return an existing promise instead of a new one, but if other solutions provide the same functionality and nicer code then please let me know. Native ES6 is preferred.

Thanks!

-

'use strict';

var p;
var q;

var randomNumber = function() {
  return new Promise(function(resolve) {
    console.log(`p: ${p}`);

    if (typeof p === 'undefined') {
      setTimeout(function() {
        p = Math.random();
        resolve(p);
      }, 1000);

    } else {
      resolve(p);
    }
  });
};

var randomNumberPlusOne = function() {
  return new Promise(function(resolve) {
    console.log(`q: ${q}`);

    if (typeof q === 'undefined') {
      randomNumber()
        .then(function(p) {
          q = p + 1;
          resolve(q);
        });

    } else {
      resolve(q);
    }
  });
};

var showResult = function(result) {
  console.log();
  console.log(`r: ${result}`);
};

randomNumber()
  .then(showResult);

randomNumber()
  .then(randomNumberPlusOne)
  .then(showResult);

randomNumberPlusOne()
  .then(showResult);

setTimeout(function() {
  console.log();
  console.log('setTimeout:');
  console.log();
  randomNumber()
    .then(showResult);

  randomNumber()
    .then(randomNumberPlusOne)
    .then(showResult);

  randomNumberPlusOne()
    .then(showResult);
}, 2000);

- (code below is based on the feedback from Bergi;

'use strict';

var randomNumber = (function() {
  var p = null;

  return function() {
    console.log('p:');
    console.log(p);
    console.log();

    if (!p) {
      p = new Promise(function(resolve) {
        setTimeout(function() {
          resolve(Math.random());
        }, 1000);
      });
    }

    return p;
  };
})();

var randomNumberPlusOne = (function() {
  var q = null;

  return function() {
    console.log('q:');
    console.log(q);
    console.log();

    if (!q) {
      q = randomNumber()
        .then(function(p) {
          return p + 1;
        });
    }

    return q;
  };
})();

var showResult = function(result) {
  console.log(`r: ${result}`);
  console.log();
};

randomNumber()
  .then(showResult);

randomNumber()
  .then(randomNumberPlusOne)
  .then(showResult);

randomNumberPlusOne()
  .then(showResult);

setTimeout(function() {
  console.log();
  console.log('setTimeout:');
  console.log();

  randomNumber()
    .then(showResult);

  randomNumber()
    .then(randomNumberPlusOne)
    .then(showResult);

  randomNumberPlusOne()
    .then(showResult);

}, 2000);
JoHe
  • 23
  • 5
  • Use a variable. That's why variables are there. So you can reference the same data multiple times. –  Aug 04 '15 at 22:01
  • var x = new Promise(); function() { return x }; – Melbourne2991 Aug 04 '15 at 22:08
  • Thanks for your answer Jamen. Maybe I have misunderstood your answer, but when you use a promise as a variable instead of a function then the code is executed immediately instead of waiting to be called when necessary. Which results in undesired side effects. Also, the then method needs a function instead of a promise to prevent promise result fall through. – JoHe Aug 04 '15 at 22:19
  • Wait, so you're expecting to return `randomNumber` and `randomNumberPlusOne` to yield the same number every time they're called, *not a random number*??? – Bergi Aug 04 '15 at 23:07

2 Answers2

3

You'll want to memoise the promise, not the value that it resolves with. Memoisation works fine with promises as result values.

var p = null;
function notSoRandomAsyncNumber() {
  if (!p)
    p = new Promise(function(resolve) {
      setTimeout(function() {
        resolve(Math.random());
      }, 1000);
    });
  return p;
}

Or, abstracted into a helper function:

function memoize(fn) {
  var cache = null;
  return function memoized(args) {
    if (fn) {
      cache = fn.apply(this, arguments);
      fn = null;
    }
    return cache;
  };
}
function randomAsyncNumber() {
  return new Promise(res => {
    setTimeout(() => resolve(Math.random()), 1000);
  });
}
function randomAsyncNumberPlusOne() {
  return randomAsyncNumber().then(n => n+1);
}
var notSoRandomAsyncNumber = memoize(randomAsyncNumber);
var notSoRandomAsyncNumberPlusOne = memoize(randomAsyncNumberPlusOne);

(notice that notSoRandomAsyncNumberPlusOne still will create a randomAsyncNumber() on the first call, not a notSoRandomAsyncNumber())

Community
  • 1
  • 1
Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • Thanks for your awesome answer Bergi! I am still very new to programming and JavaScript and I wasn't aware that this technique is called memoization. After your answer I feel stupid for returning the value instead of the promise. Based on your answer I have created a proof-of-concept based on closures to remove the variables from the global scope. What is your opinion about this method? (I needed to add the new code to the question because I wasn't allowed to put in in the comment, I'm also new to Stack Overflow) – JoHe Aug 05 '15 at 20:41
  • Yes, using IEFEs is fine to create a scope for `p` and `q`. Only it feels a bit repetitive, that's what I've written `memoize` for :-) – Bergi Aug 05 '15 at 20:43
  • ah, now the noob understands that you were also using closures. ;-) (another I feel stupid moment) Thanks again and hopefully one day I have your JavaScript skills and are able to help people as well. – JoHe Aug 05 '15 at 20:53
-1

Try the new variable approach suggested. Promises are designed to be single-shot, so it depends on the implementation. What you are trying to do is more in the arena of events or reactivex. If you are using jquery then you can use their event scheme. A cleaner one is available on the nodejs site. Since it does not require anything else it runs in the browser. rxjs is the cream of the stream processing crop, but it is a big library and requires some learning. Worth it - since the same knowledge is useful client and server in many languages. You can even set up a stream from a promise - among many other ways.

Paul Marrington
  • 557
  • 2
  • 7