Pseudo Promise.all() polyfill
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-&gt; void)) -&gt; 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) =&gt; // &lt;-- two promises: "p of x" and "p of y"
  px.then(x =&gt; py.then(y =&gt; [ x, y ])) // two values: "x" and "y"

const all = (promises = []) =&gt;
  promises.reduce
    ( (pr, px) =&gt;
        and(pr, px) // &lt;-- two promises: "p of r" and "p of x"
          .then(([ r, x ]) =&gt; // &lt;-- two values: "r" and "x"
            [ ...r, x ]) // &lt;-- result: append "x" to "r"
    , Promise.resolve([]) // &lt;-- initial result, "p of empty"
    )


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) =&gt;
  px.then(x =&gt; py.then(y =&gt; [ x, y ]))

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

const delay = (ms, x) =&gt;
  new Promise(r =&gt; 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'),
  delay(200, 'b'),
  Promise.reject(Error('bad things happened')),
  delay(50, 'c'),
  delay(1000, 'd'),
])
.then(console.log, console.error) // Error: bad things happened




But we can do better. Putting x and y in an array [] is an extra "thing". And the operation we are using in reduce, [ ...r, x ] is also a well-known function -

const and = (px, py) =&gt;
  px.then(x =&gt; py.then(y =&gt; [ x, y ])) // hard-coded []

const all = (promises = []) =&gt;
  promises.reduce
    ( (pr, px) =&gt;
        and(pr, px)
          .then(([ r, x ]) =&gt;
            [ ...r, x ]) // &lt;-- is known as "append"
    , Promise.resolve([])
    )


Instead of locking x and y in an array, [ x, y ], we could specify any behaviour we want, ie doSomething(x, y) -



const and = (doSomething, px, py) =&gt;
  px.then(x =&gt; py.then(y =&gt; doSomething(x, y)))

const myFunc = (a, b) =&gt;
  (a + b) * 100

and(myFunc, Promise.resolve(1), Promise.resolve(2))
  .then(console.log, console.error) // 300




This implementation of and is well known by another name. lift2 allows us to take an ordinary function (eg append) and use it in (ie "lift it into") the Promise context -

// complete polyfill for Promise.all

const lift2 = (f, px, py) =&gt;
  px.then(x =&gt; py.then(y =&gt; f(x, y)))

const append = (a = [], x = null) =&gt;
  [...a, x]

const all = (promises = []) =&gt;
  promises.reduce
    ( (pr, px) =&gt; lift2(append, pr, px)
    , Promise.resolve([])
    )


Expand the snippet to verify the results in your browser -



const lift2 = (f, px, py) =&gt;
  px.then(x =&gt; py.then(y =&gt; f(x, y)))

const append = (a = [], x = null) =&gt;
  [...a, x]

const all = (promises = []) =&gt;
  promises.reduce
    ( (pr, px) =&gt; lift2(append, pr, px)
    , Promise.resolve([])
    )

const delay = (ms, x) =&gt;
  new Promise(r =&gt; 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'),
  delay(200, 'b'),
  Promise.reject(Error('bad things happened')),
  delay(50, 'c'),
  delay(1000, 'd'),
])
.then(console.log, console.error) // Error: bad things happened






why is it named lift2?

It's called lift2 because the function we lifted (append in this example) expects two arguments. In typed languages, it's common to see lift3 and lift4 variants -



const lift3 = (f, px, py, pz) =&gt; // &lt;-- 3 promises
  px.then(x =&gt; py.then(y =&gt; pz.then(z =&gt; 
    f(x,y,z) // &lt;-- 3 values
  )))

const lift4 = (f, pw, px, py, pz) =&gt; // &lt;-- 4 promises
  pw.then(w =&gt; px.then(x =&gt; py.then(y =&gt; pz.then(z =&gt; 
    f(w,x,y,z) // &lt;-- 4 values
  ))))
  
const p = x =&gt; // &lt;-- helper for demo
  Promise.resolve(x) 

const add = (...numbers) =&gt;
  numbers.reduce((a, b) =&gt; a + b, 0)

lift3(add, p(1), p(2), p(3))
  .then(console.log, console.error) // 6

lift4(add, p(1), p(2), p(3), p(4))
  .then(console.log, console.error) // 10




But JavaScript is a dynamic language and so we do not need such strict implementations. We can implement liftN which accepts a variadic function -



const append = (a = [], x = null) =&gt;
  [...a, x]

const lift2 = f =&gt;
  (px, py) =&gt; px.then(x =&gt; py.then(y =&gt; f(x, y)))

const liftN = (f, ...promises) =&gt;
  promises
    .reduce(lift2(append), Promise.resolve([])) // &lt;-- !
    .then(values =&gt; f(...values))

const p = x =&gt; // &lt;-- helper for demo
  Promise.resolve(x) 

const add = (...numbers) =&gt;
  numbers.reduce((a, b) =&gt; a + b, 0)

liftN(add, p(1))
  .then(console.log, console.error) // 1

liftN(add, p(1), p(2))
  .then(console.log, console.error) // 3

liftN(add, p(1), p(2), p(3))
  .then(console.log, console.error) // 6

liftN(add, p(1), p(2), p(3), p(4))
  .then(console.log, console.error) // 10




Notice ! above. liftN is essentially the variadic version of all. The two can be used interchangeably -

const all = (promises = []) =&gt;
  promises.reduce(lift2(append), Promise.resolve([]))

const liftN = (f, ...promises) =&gt;
  all(promises).then(values =&gt; f(...values))


all([ p(1), p(2), p(3), p(4) ])
  .then(nums =&gt; add(...nums))
  .then(console.log, console.error) // 10

liftN(add, p(1), p(2), p(3), p(4))
  .then(console.log, console.error) // 10


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

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


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



const append = (a = [], x = null) =&gt;
  [...a, x]

const lift2 = f =&gt;
  (px, py) =&gt; px.then(x =&gt; py.then(y =&gt; f(x, y)))

const all = (promises = []) =&gt;
  promises.reduce(lift2(append), Promise.resolve([]))

const liftN = (f, ...promises) =&gt;
  all(promises).then(values =&gt; f(...values))

const delay = (ms, x) =&gt;
  new Promise(r =&gt; setTimeout(r, ms, x))

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

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