0

Background

Developing a Chrome extension (latest Chrome running on Mac OS Sierra) and I can't work out how to loop over an array which is also dynamically built at runtime.

Forgive me if I am missing something really obvious, but I cannot for the life of me work out why this for..of loop is not being entered.

I've also tried a for..in and the good old classic for loop structure i.e. for (let i = 0; i < array.length; i++) - no matter what style of loop this block is never entered, despite my array at runtime reportedly having a single item in it.

Problem Code and Statement

This code gets all files inside a directory and slices off the last 3 chars (to remove .js):

const getDirectoryContents = (path) => {
  let fileNames = []

  chrome.runtime.getPackageDirectoryEntry( (directoryEntry) => {
    directoryEntry.getDirectory(path, {}, (subDirectoryEntry) => {
      const subDirectoryReader = subDirectoryEntry.createReader()
      subDirectoryReader.readEntries( (entries) => {
        for (const entry of entries) {
          fileNames.push(entry.name.slice(0, -3))
        }
      })
    })
  })

  return fileNames
}

From inside the chrome.runtime.onStartup() callback function we want to add some context menus, which we do like so:

const addContextMenus = () => {
  console.log(getDirectoryContents('commands'))
  for (const command of getDirectoryContents('commands')) {
    const properties = {
      id: command,
      title: command,
      contexts: ['editable']
    }
    chrome.contextMenus.create(properties)
    console.log(`Created context menu ${properties.title}`)
  }
  console.log('End addContextMenus')
}

Now, during runtime, the above code will output this inside the background page console:

Console log screenshot

However as we can see (due to the lack of the console logging "Created context menu ..." - the loop is never entered, despite the array having a length of 1.

I've found nothing online inside the Chrome developer docs that indicated that getDirectoryContents is asynchronous -- which would be one possible explanation -- but just to be sure I even tried adding a callback param to the getDirectoryContents function to ensure we are looping after the array has been populated.

EDIT: after closer inspection of the original function, it's clear that the array is in fact being returned before is has a chance to be populated by the directory reader. Answer below.

Same result!

Any help would be much appreciated. Thanks for your time.

GrayedFox
  • 2,350
  • 26
  • 44
  • 1
    you have a typical async issue (returning empty array before callback finishes). dont just throw a callback to the mix, follow the code using debugging breakpoints and study an async guide. – Zig Mandel May 25 '17 at 22:14
  • 1
    Possible duplicate of [How do I return the response from an asynchronous call?](https://stackoverflow.com/questions/14220321/how-do-i-return-the-response-from-an-asynchronous-call) – Daniel Herr May 25 '17 at 23:14
  • The console output hints that this might not be an async issue - the first line outputs an array with length 1 before the end of the function outputs "End addContextMenus. Furthermore I did try this by adding a callback function, I will post the code with the callback so that you can see how I did it. – GrayedFox May 29 '17 at 10:05
  • Mmmm... just found [this](https://developer.mozilla.org/en-US/Add-ons/WebExtensions/API/runtime/getPackageDirectoryEntry) on MDN - maybe slightly different (in that it doesn't return a promise) but perhaps it is async after all. Will update post with findings after work. – GrayedFox May 29 '17 at 10:10

1 Answers1

0

How embarrassing! Passing in a callback function and executing it at the right time solved it. Comments were all correct - typical async issue - thanks for the support.

The problem was on line 15 of the original function: I was returning the array before it had a chance to be populated.

Working function:

const getDirectoryContents = (path, callback) => {
  chrome.runtime.getPackageDirectoryEntry( (directoryEntry) => {
    directoryEntry.getDirectory(path, {}, (subDirectoryEntry) => {
      const subDirectoryReader = subDirectoryEntry.createReader()
      let fileNames = []

      subDirectoryReader.readEntries( (entries) => {
        for (const entry of entries) {
          fileNames.push(entry.name.slice(0, -3))
        }

        callback(fileNames)
      })
    })
  })
}
GrayedFox
  • 2,350
  • 26
  • 44