1

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.

13
  • Try adding the console.log(arr)) inside the first .then Commented Oct 7, 2019 at 5:08
  • The final .then() should be printing undefined, not an array. And you should see its output before anything else. Commented Oct 7, 2019 at 5:08
  • @mph85 The problem with sticking a console.log inside the first then is that fsPromises.stat is asynchronous as well. I'm completely new to Node, Electron, FS, and Promises, so this is really throwing me for a loop :( Commented Oct 7, 2019 at 5:12
  • @PatrickRoberts oh shit, sorry. in my code i have let arr = [], and youre right that its running first for some reason. isnt the whole purpose of promises to run the thens sequentially? Commented Oct 7, 2019 at 5:13
  • 2
    @BugWhisperer you are not waiting for the promises created inside the loop. You should group them and wait for them to finish in a Promise.all. After that, your then will have correct values. Commented Oct 7, 2019 at 5:17

1 Answer 1

3

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. :P

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();
Sign up to request clarification or add additional context in comments.

10 Comments

Cool. I'd probably rather implement it myself rather than include another dependency. It's probably going to take me some time to digest all of this since the whole concept of promises is very new to me and I've been staring at a screen for far too long.
Wow, Promise.all did work in your first example. Shouldn't it work anyways since Node is JS after all? Or Is there a reason why it wouldn't have worked?
Oh, I guess I was remembering an old version...? Or a brain fart, who knows. I guess I should delete that paragraph.
The async await syntax is so much more intuitive and easily understood imo. is that just due to me not being used to promises? which syntax seems more straightforward to you?
As I said, async/await was created in order to simplify the promise syntax and make it look almost like synchronous code. It is still dealing with promises though, so if you don't understand promises, it is kind of easy to mess up with async/await.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.