0

i have a gen-toc.mjs script that contains my async loop:

/**
 * node imports
 */
import fs from 'fs'
import path from 'path'

/**
 * unified imports
 */
import { unified } from 'unified'
import { visit } from 'unist-util-visit'

/**
 * remark imports
 */
import { remark } from 'remark'
import remarkRehype from 'remark-rehype'
import remarkParse from 'remark-parse'
import remarkToc from 'remark-toc'

/**
 * rehype imports
 */
import rehypeDocument from 'rehype-document'
import rehypeStringify from 'rehype-stringify'
import rehypeRewrite from 'rehype-rewrite'
import rehypeFormat from 'rehype-format'

/**
 * npm imports
 */
import slugify from '@sindresorhus/slugify'

/**
 * local imports
 */
import vivliostyleConfig from '../vivliostyle.config.js'

const readMarkdownFiles = async (mdFilePaths, entryContext) => {
  return await mdFilePaths
    .map(async (md) => {
      const content = await fs.promises.readFile(
        path.join(entryContext, md),
        'utf8'
      )
      console.log({ content })
      return content
    })
    .join('')
}

const main = async () => {
  // read entry from vivliostyle.config.js in order & then change toc.html
  const { title, entry, entryContext, toc, tocTitle } = vivliostyleConfig

  let tocCss = ''

  const mdFilePaths = entry.filter((e) => {
    if (typeof e === 'string') {
      return e
    } else {
      if (e.rel === 'contents') {
        tocCss = e.theme
      }
    }
  })

  const markup = await readMarkdownFiles(mdFilePaths, entryContext)

  console.log({ markup })

  const tree = unified().use(remarkParse).parse(markup)

  visit(tree, (node) => {
    if (node.type === 'heading') {
      console.log(node.children)
    }
  })
}

main()

but it somehow throws an error.

the relevant code looks like:

const readMarkdownFiles = async (mdFilePaths, entryContext) => {
  return await mdFilePaths
    .map(async (md) => {
      const content = await fs.promises.readFile(
        path.join(entryContext, md),
        'utf8'
      )
      console.log({ content })
      return content
    })
    .join('')
}

const main = async () => {
  // read entry from vivliostyle.config.js in order & then change toc.html
  const { title, entry, entryContext, toc, tocTitle } = vivliostyleConfig

  let tocCss = ''

  const mdFilePaths = entry.filter((e) => {
    if (typeof e === 'string') {
      return e
    } else {
      if (e.rel === 'contents') {
        tocCss = e.theme
      }
    }
  })
  // mdFilePaths returns ['chapter1/index.md', 'chapter2/index.md']

  const markup = await readMarkdownFiles(mdFilePaths, entryContext)

  console.log({ markup })
}

but when i run this, i get the following:

{ markup: '[object Promise][object Promise]' }
{
  content: '# chatper2\n' +
    '\n' +
    '## generate pdf, epub, and mobi\n' +
    '\n' +
    "first let's generate pdf.\n" +
    '\n' +
    "then we'll generate an epub.\n" +
    '\n' +
    "finally, we'll generate a mobi.\n" +
    '\n' +
    '![lion](./lion.png){width=100%}\n'
}
{
  content: '# chapter1\n' +
    '\n' +
    '## write a book in markdown\n' +
    '\n' +
    'have a frontmatter.\n' +
    '\n' +
    'frontmatter must contain `title`, `description`, `date`, and `author`.\n' +
    '\n' +
    '![tiger](./tiger.png){width=100%}'
}

it generates the content loop afterwards of markup as the markup is logged first.

i have tried every single answer & tried many different ways like appending to a string but none of them worked.

i know node.js works like this but unable to solve this even though it's basics.

all i want is to be able to read files contents in order & then append them in one variable markup.

full repro → https://github.com/deadcoder0904/vivliostyle/ (just do npm i and npm run prebuild to run the gen-toc.mjs script)

how can i solve it?

deadcoder0904
  • 7,232
  • 12
  • 66
  • 163
  • 1
    _“but it somehow throws an error”_ — Which error? `mdFilePaths.map(async (md) => {`…`})` is an array of Promises. You then immediately call `.join` on it which produces a string. Only then do you await the string (which is useless). You should use `return (await Promise.all(mdFilePaths.map(async (md) => {`…`}))).join("")`. – Sebastian Simon Oct 24 '22 at 11:40
  • 1
    Your `map` callback is an `async` function, so it [returns a promise](https://stackoverflow.com/questions/51338277/async-function-returning-promise-instead-of-value). You're then calling `join` on the resulting array of promises. Promises are objects that have no special implementation of `toString`, so you get `"[object Promise]"` for each promise in the array, joined together. You have to wait for those promises to settle and use the result, probably via `Promise.all`: https://pastebin.com/t32FsRqj (or similar) – T.J. Crowder Oct 24 '22 at 11:40
  • @SebastianSimon @T.J. Crowder both of you gave the same solution which i tried already as it was the common answer for my question. it gave me this error: `TypeError: Promise.all(...).join is not a function`... how do i solve it? – deadcoder0904 Oct 24 '22 at 11:47
  • 3
    @deadcoder0904 No, `(await Promise.all(`…`)).join(`…`)`. Grouping the expressions with parentheses is important because property access has a higher precedence than `await`; see [What is the Operator Precedence of Await?](/q/48218744/4642212). – Sebastian Simon Oct 24 '22 at 11:48

0 Answers0