Search code examples
javascriptnode.jsasynchronousasync-awaitxlsx

Pulling a file from an API and piping it results in a file not found error when I try to read it


I'm pulling an Excel file from an API, and need to read and modify it using the xlsx library. I'm using node 8.10 and async/await for this:

const report = await getVisitorReport(visitorQueryString, 'test12.xls', uri);

let workbook = XLSX.readFile('test12.xls');

And here's the function getVisitorReport. Notice that I resolve the promise on finish for the pipe:

async function getVisitorReport(queryString, reportPath, uri) {
return new Promise((resolve, reject) => {
request({
  url: uri,
  qs: queryString,
  headers: {
    'Content-Type': 'application/json',
    'Accept': 'application/json',
    'Authorization': 'Basic ' + new Buffer(process.env.username + ':' + process.env.password, 'utf8').toString('base64')
  }
}, (error, response, body) => {
  if (error) {
    reject(error);
  } else {
    if (response.statusCode === 200) {
      resolve(body);
    } else if (response.statusCode === 409) {
      setTimeout(() => {
        resolve(getVisitorReport(queryString));
      }, response.headers['Retry-After'] * 1000);
    } else {
      reject(response);
    }
  }
}).pipe(fs.createWriteStream(reportPath)).on('finish', resolve(reportPath));
});
}

The file is pulled and created correctly. It seems that the second line XLSX.readFile('test12.xls') happens before the file is done being saved locally. What am I doing wrong here? How do I make sure the file is saved before I try to read it? Why is .pipe.on('finish', resolve) not accomplishing this? Thanks for the help!


Solution

  • The code immediately calls resolve here: .on('finish', resolve(reportPath)).

    You can either provide the resolve function directly to the on handler :

    .on('finish', resolve)
    

    Or if you need to pass some data, use an arrow function:

    .on('finish', () => resolve(args))
    

    edit:

    if you do it like this: .on('finish', resolve(reportPath)) it roughly equals to

    resolve(reportPath); // immediately calls resolve which makes a promise resolved 
                         // so await doesn't stop the further code execution
    ....on('finish', undefined)
    

    To better understand look at this example:

    const [bad, good] = document.querySelectorAll("button");
    
    bad.addEventListener("click", console.log("I log immediately when the page loads and not at click event"))
    
    good.addEventListener("click", () => console.log("I log properly when button is clicked"))
    <button>broken button</button>
    <button>good button</button>