Home > OS >  How do properly chain these .then statements so they resolve in the correct order?
How do properly chain these .then statements so they resolve in the correct order?

Time:02-04

The assignment I'm working on is to design a Node.js program that presents the user with command line prompts and writes a well-organized README markdown file with the input. The prompts are created with the Inquirer NPM. The code starts with the Inquirer prompts, then one or multiple .then statements create the file using FS's "writeFile" and "appendFile," and add elements in a particular order. For some reason, no matter what I do, I can't seem to get my code to add these elements to the new file in the proper order. I originally had everything under one .then statement, but then I figured the bug would be fixed if I gave each item to append its own ".then" and chained them together. But the output still comes out wrong -- in fact it seems to end up coming out in a different random order each time. Here's a few of these .then statements -- the whole block is pretty long so hopefully this provides enough context. You can also imagine this appended to a series of Inquirer prompts.

  .then((response) => {
    fs.writeFile('./output/README.md', '', (err) => err ? console.log(err) : null);
    return response;
  })
  .then((response) => {
    if(response.title) {
        fs.appendFile('./output/README.md', `# ${response.title}\n`, (err) => err ? console.log(err) : null);
    } else {
        console.log('ALERT: No title provided.');
    }
    return response;
  })
  .then((response) => {
    if(response.license) {
        for(const i of licenses) {
            if(i.name == response.license) {
                fs.appendFile('./output/README.md', `${i.badge}\n`, (err) => err ? console.log(err) : null);
            }
        }
    }
    return response;
  })
  .then((response) => {
    if(response.desc) {
        fs.appendFile('./output/README.md', `## Description\n${response.desc}\n`, (err) => err ? console.log(err) : null);
    } else {
        console.log('ALERT: No description provided.');
    }
    return response;
  })

TL;DR I tried chaining some .then statements to get this code to write a markdown file with a particular ordered structure, but it's still executing in an incorrect, seemingly random order.

CodePudding user response:

For asynchronous operations that needs to be executed one by one - it is important to actually await the "notification" about previous operation is complete before starting next one. You are not awaiting anything.

The fs methods you are using are asynchronous and they are callback based. To simplify things and to be able to rely on them with .then functions - you need to promisify (example) those functions and handle their returning promises correctly.

Preferred way is to use fs.promises api, like this one. Or fs sync api, like this one (not recommended but anyway, still a solution).

Example:

const fs = require("fs");

// ...
.then(async (response) => {
  const filePath = "./output/README.md";
  await fs.promises.writeFile(filePath, "");
  if (response.title) {
    await fs.promises.appendFile(filePath, `# ${response.title}\n`);
  } else {
    console.log("ALERT: No title provided.");
  }
  if (response.license) {
    for (const i of licenses) {
      if (i.name === response.license) {
        await fs.promises.appendFile(filePath, `${i.badge}\n`);
      }
    }
  }
  if (response.desc) {
    await fs.promises.appendFile(
      filePath,
      `## Description\n${response.desc}\n`
    );
  } else {
    console.log("ALERT: No description provided.");
  }
  return response;
});

CodePudding user response:

UPDATE: Of course as soon as I resort to Stackoverflow for help I answer my own question. My solution was simply not to chain multiple .then statements, instead using one writeFile function with a lengthy template literal, all in one .then statement, to create the markdown file. Like so:

.then((response) => {
    let badge;
    let toc = '## Table of Contents\n1. [Installation](#installation)\n2. [Usage](#usage)\n3. [License](#license)\n4. [Contributors](#contributors)\n5. [Questions](#questions)';
    for(const i of licenses) {
        if(i.name == response.license) {
            badge = i.badge;
        }
    }
    fs.writeFile('./output/README.md',
        `# ${response.title}\n${badge}\n# Description\n${response.desc}\n${toc}\n# Installation\n${response.install}\n# Usage\n${response.usage}\n# License\n${response.license}\n# Contributors\n${response.contr}\n# Questions\nGitHub username: ${response.github}\nEmail address: ${response.email}`,
        (err) => err ? console.log(err) : null);
  })

This doesn't solve the mystery of why those chained .thens weren't working, but in hindsight it was excessive. I may have to embellish this code a bit to make sure it doesn't produce errors if an input is missed, but it should all fit together in this one statement.

  • Related