Sorting Array Returns Array Out of Order

Welcome to Programming Tutorial official website. Today - we are going to cover how to solve / find the solution of this error Sorting Array Returns Array Out of Order on this date .

I’m so confused right now. I’e spent the last hour and a bit trying to sort directories based on their creation date. Here is where I’m at:

const
  fsPromises = require('fs').promises
let
  arr = []

fsPromises.readdir(rootPath, 'utf8')
.then((array)=>{

  array.forEach((dir)=>{
    console.log(dir) // executed last for some reason
    fsPromises.stat(`./Projects/${dir}`)
    .then((stats)=>{
      arr.push([stats.birthtimeMs, dir])
    })
  })

})
.then(()=>{
  arr = arr.sort((a,b)=>{
    Math.floor(a[0])-Math.floor(b[0])
  })
})
.then(console.log(arr))

I have no idea why the final then is spitting out an unordered array.

Promises are new to me, so I’m not entirely sure if it’s the promise chain that’s causing the issue, but everything seems good up until the second then.

Any help would be greatly appreciated.

Answer

It is the promise chain: if the chain is broken anywhere, it won’t work.

You seem to be mixing up the two arrow forms: (params) => expression and (params) => { statements }. Everything in your code is expressible with the former, so I went with that. If you convert to the statement block form, do not forget to return (which the expression form does implicitly).

Because you don’t return the promises from the arrow statement block, the next promise is not stitched to the previous one, so there is no waiting going on and things execute much more synchronously than intended. Additionally, if you have multiple promises, you need to wait till they are all ready to fulfill by using Promise.all. It will create a promise that all the sub-promises are done, and return an array of results of sub-promises.

fsPromises.readdir(rootPath, 'utf8')
.then(array => Promise.all(
    array.map(dir =>
        fsPromises.stat(`./Projects/${dir}`)
        .then(stats => [stats.birthtimeMs, dir])
    )
))
.then(arr =>
  arr.sort((a, b) =>
    Math.floor(a[0]) - Math.floor(b[0])
  )
)
.then(arr => console.log(arr))

(I did not check this, so there might be accidental dragons.)

So first readdir promises an array of files. We will map this array so that for each filename, stat makes a promise of its stats, and we chain it into a promise of tuples. So array.map now returns an array of tuple promises. Promise.all receives the array of promises, and promises an array of results of those promises when they are all done. We chain this into a promise of a sorted array, then chain it into a promise of console logging the array.

EDIT: I am not sure, but I don’t think Promise.all exists in Node.js core. There is any number of promise packages for Node.js that do include Promise.all, pretty much any of them will do (e.g. Bluebird). Or, you could implement it yourself, it’s not that big.

EDIT2: One could also suggest you switch to async/await if it’s supported in your environment, as it makes code much more similar to what you are used to. However, some understanding of promises is still needed, as it’s all promises under the hood. 😛

async function statDir() {
  let files = await fsPromises.readdir(rootPath, 'utf8');
  let stats = [];
  for (let file of files) {
    let stat = await fsPromises.stat(`./Projects/${file}`);
    stats.push([stat.birthtimeMs, file]);
  }
  stats.sort((a, b) =>
    Math.floor(a[0]) - Math.floor(b[0])
  );
  console.log(stats);
}
statDir();