HiveBrain v1.2.0
Get Started
← Back to all entries
patternjavascriptMinor

Pseudo Promise.all() polyfill

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
allpolyfillpromisepseudo

Problem

A few years back I interviewed with a company for a Javascript position. After a couple of warm-up challenges I was presented with this:


Please write a function that calls back with true if all
promises have resolved successfully, or false if at least one
promise has rejected.


Given 'Promise' API:

promise.then(
  function resolve() { /* called when some async thing was successful */},
  function reject() { /* the async thing failed */ }
);


and also was given the following function structure and mocking code:

function all (promises, callback) {
    // TODO call back with `true` if all promises resolve(), or `false` if a promise has reject()ed
    promises.forEach(function (promise) {
        promise.then(
          function resolve() {},
          function reject() {}
        );
    });
}

// Some mocking code (NO NEED TO READ THIS):
function P() { return { then: function (resolve, reject) {
  setTimeout(function() { (5*Math.random()|0) ? resolve() : reject() }, Math.random()*1000);
}}}
var promises = [1,2,3,4,5].map(P);

all(promises, function (success) {
  console.log('The promises have ' + ( success ? '' : 'not ' ) + 'all resolved!' )
});


The gist of it: I had to write an almost Promise.all() method that would check if all async functions finished and how they finished (resolve/reject).

I didn't finish the challenge in the allocated time frame, so I failed (and years later, looking at the code I've written to finally solve it, if was the interviewer, I would failed me even if I was done in time...)

My current implementation

A few days ago I found the challenged buried on the hard drive and decided to give it a go (I timed myself to finish it in time):

Does not re-include the mocking code from above, but it is included in the jsbin below

```
function all (promises, callback) {
const promisesStatus = [];
const allPromisesChecked = (promisesArray = promises, promisesStatusArray = promisesStatus) => promisesStat

Solution

implementation

  • The biggest thing that jumped out at me is your all function takes a callback instead of returning a Promise like Promise.all would do. (EDIT: it looks like the interview asked that of you, so that makes sense then. A section has been added below.)



  • When your handling the reject branch of each promise (in the .then call), there's no reason to do anything fancy. As soon as you encounter an error, you can immediately reject the outer promise (or in your case, callback with an Error



  • allPromisesChecked and allPromisesPassed gets a little verbose but as long as it works it kinda doesn't matter



This interested me so I took a shot at implementing it. Here's my code -



// Promise.all polyfill
function all(promises) {
return new Promise(function(resolve,reject) {
var count = promises.length
var result = []
var checkDone = function() { if (--count === 0) resolve(result) }
promises.forEach(function(p, i) {
p.then(function(x) { result[i] = x }, reject).then(checkDone)
})
})
}

// delay helper for creating promises that resolve after ms milliseconds
function delay(ms, value) {
return new Promise(function(pass) {
setTimeout(pass, ms, value)
})
}

// resolved promises wait for one another but ensure order is kept
all([
delay(100, 'a'),
delay(200, 'b'),
delay(50, 'c'),
delay(1000, 'd')
])
.then(console.log, console.error) // [ a, b, c, d ]

// check that error rejects asap
all([
delay(100, 'a'),
delay(200, 'b'),
Promise.reject(Error('bad things happened')),
delay(50, 'c'),
delay(1000, 'd')
])
.then(console.log, console.error) // Error: bad things happened




time

This took me about 10 minutes. If someone already has experience with Promises, I would expect someone could come up with a working solution in less than 30 minutes. If you've never seen Promises before, maybe 60 minutes?

using a callback

Re-reading the question, I see that my original answer is an actual polyfill of Promise.all, not what the interview asked of you.

Here's a dramatically simplified function that is essentially useless except for answering the interview question.



// Promise.all wannabe
// ([Promise], (bool-> void)) -> void
function all(promises, callback) {
var count = promises.length
promises.forEach(function(p, i) {
p.then(
function() { if (--count === 0) callback(true) },
function() { callback(false) }
)
})
}

// delay helper for creating promises that resolve after ms milliseconds
function delay(ms, value) {
return new Promise(function(pass) {
setTimeout(pass, ms, value)
})
}

// basic boilerplate to check an answer
function checkAnswer(label, promises) {
all(promises, function(result) {
console.log(label, result)
})
}

// resolved promises wait for one another but ensure order is kept
checkAnswer('example1', [
delay(100, 'a'),
delay(200, 'b'),
delay(50, 'c'),
delay(1000, 'd')
]) // [ a, b, c , d ]

// check that error rejects asap
checkAnswer('example2', [
delay(100, 'a'),
delay(200, 'b'),
Promise.reject(Error('bad things happened')),
delay(50, 'c'),
delay(1000, 'd')
]) // Error: bad things happened




remarks

So in hindsight, I do have some more critique to offer. Considering the function only has to return true or false, there's no reason to make it complex. Basically you just have to count the resolve branches until it reaches the count of promises provided as input. If a reject happens, you can immediately return false. There's no need for any other code.

back from the future (4 years later)

Functions have taught me a lot over the recent years. Small functions that do one thing are always better than big functions that do many. The design for this implementation starts with the simple idea of combining two promises into an array of two values. We'll call it and -

const and = (px, py) => // py.then(y => [ x, y ])) // two values: "x" and "y"

const all = (promises = []) =>
promises.reduce
( (pr, px) =>
and(pr, px) // //

Already we're done implementing
all and behaviour is the same -

all([
delay(100, 'a'),
delay(200, 'b'),
delay(50, 'c'),
delay(1000, 'd'),
])
.then(console.log, console.error)
// [ a, b, c, d ]

all([
delay(100, 'a'),
delay(200, 'b'),
Promise.reject(Error('bad things happened')),
delay(50, 'c'),
delay(1000, 'd'),
])
.then(console.log, console.error)
// Error: bad things happened


Expand the snippet below to verify the result in your browser -



const and = (px, py) =>
px.then(x => py.then(y => [ x, y ]))

const all = (promises = []) =>
promises.reduce
( (pr, px) =>
and(pr, px).then(([ r, x ]) => [ ...r, x ])
, Promise.resolve([])
)

const delay = (ms, x) =>
new Promise(r => setTimeout(r, ms, x))

all([
delay(100, 'a'),
delay(200, 'b'),
delay(50, 'c'),
delay(1000, 'd'),
])
.then(console.log, console.error) // [ a, b, c, d ]

all([
delay(100, 'a'),
del

Context

StackExchange Code Review Q#134224, answer score: 8

Revisions (0)

No revisions yet.