1

I'm fetching the stylesheet and replacing all CSS variables with the actual hex value it corresponds to when the user changes the color as desired.

I created an event handler so that when the user clicks the download button, all of the colors he/she selected, would be saved in the stylesheet at that moment, but it doesn't seem to work. I know it's an issue with my understanding of promises as a whole and async await

What I did.

const fetchStyleSheet = async () => {
  const res = await fetch("./themes/prism.css");
  const orig_css = await res.text();
  let updated_css = orig_css;

  const regexp = /(?:var\(--)[a-zA-z\-]*(?:\))/g;
  let cssVars = orig_css.matchAll(regexp);
  cssVars = Array.from(cssVars).flat();
  console.log(cssVars)

  for await (const variable of cssVars) {
    const trimmedVar = variable.slice(6, -1)
    const styles = getComputedStyle(document.documentElement)
    const value = String(styles.getPropertyValue(`--${trimmedVar}`)).trim()

    updated_css = updated_css.replace(variable, value);
  }
  console.log(updated_css)

  return updated_css
}

const main = async () => {
  const downloadBtn = document.getElementById('download-btn')
  downloadBtn.addEventListener('click', () => {
    const updated_css = fetchStyleSheet()
    downloadBtn.setAttribute('href', 'data:application/octet-stream;charset=utf-8,' + encodeURIComponent(updated_css))
    downloadBtn.setAttribute('download', 'prism-theme.css')
  })
}

main()

I can't await the updated_css because it falls into the callback of the click event, which is a new function.

Then I did the following thinking it would work since it was top level.

const downloadBtn = document.getElementById('download-btn')
downloadBtn.addEventListener('click', async () => {
  const updated_css = await fetchStyleSheet()
  downloadBtn.setAttribute('href', 'data:application/octet-stream;charset=utf-8,' + encodeURIComponent(updated_css))
  downloadBtn.setAttribute('download', 'prism-theme.css')
})

That gave me the following error TypeError: NetworkError when attempting to fetch resource.

I understand that calling fetchStyleSheet() only returns a promise object at first and to get the value (which is updated_css), I need to follow it with .then() or await it.

Brian Tompsett - 汤莱恩
  • 5,753
  • 72
  • 57
  • 129
Dave.Q
  • 369
  • 1
  • 6
  • 21
  • "NetworkError when attempting to fetch resource" implies that `fetch("./themes/prism.css");` is throwing a network error and has nothing to do with the user of promises. – Quentin Jan 19 '20 at 15:24
  • @Quentin the NetworkError only occurs when I make the event handler an async function, otherwise I get a promise object if I remove `async` from it. – Dave.Q Jan 19 '20 at 15:28
  • @StephenCollins it's being served to the client, not from a separate endpoint. It's an existing CSS file within the project. – Dave.Q Jan 19 '20 at 15:30

1 Answers1

4

The await is the correct approach to deal with the fetchStyleSheet() call returning a promise, your problem is that the click on the link tries to follow the href attribute immediately - before you set it to that data url. What you would need to do instead is prevent the default action, asynchronously do your stuff, and then re-trigger the click when you're done. Also don't forget to deal with possible exceptions:

const downloadBtn = document.getElementById('download-btn')
downloadBtn.addEventListener('click', async (event) => {
  if (!e.isTrusted) return // do nothing on the second run
  try {
    event.preventDefault()
    const updated_css = await fetchStyleSheet()
    downloadBtn.setAttribute('href', 'data:application/octet-stream;charset=utf-8,' + encodeURIComponent(updated_css))
    downloadBtn.setAttribute('download', 'prism-theme.css')
    downloadBtn.click() // simulate a new click
  } catch(err) {
    console.error(err) // or alert it, or put the message on the page
  }
})
Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • Thank you so much. Is this something I could test to find out that it was trying to follow the `href` immediately? Just for future reference. – Dave.Q Jan 19 '20 at 16:13
  • @Dave.Q Events are always acted upon immediately, event handlers are expected to run synchronously. If you're starting asynchronous stuff within them, you need to handle it explicitly. – Bergi Jan 19 '20 at 16:18