简体   繁体   中英

Chaining async functions with promises Nodejs

var fs = require('fs');
var node_dir = require('node-dir');
var bluebird = require('bluebird');
var moviesClient = new ApiClient(...)
var musicClient = new ApiClient(...)
var lib = require('./index.js');

var generateMovieMetaData = async function(){
  var json = { movies: [] };

  node_dir.files(path, function(err, files) {

    bluebird.mapSeries(files, function(file){

    return moviesClient.send(new lib.requests.Movie(file))
       .then((movie) => {
      // movie is json
      /* do some loops and work with the movie json*/
      json.movies.push(movie);
       });
    })
    .then(function(movies){
      fs.writeFile('./movies.json', JSON.stringify(json), 'utf8', (err)=>{
         if(err) console.log(err)
         else { 
          console.log('File saved');
          }
      })
      return json; // go to the next function if any
    })
    .catch(function(err){
      console.log("Movie metadata could not be generated due to some error", err);
    });
  });
};

var generateMusicMetaData = async function(){
  var json = { music: [] };

  node_dir.subdirs(config.music.path, function(err, subdirs) {
    if (err) throw err;

    bluebird.mapSeries(subdirs, function(dir){

    return musicClient.send(new lib.requests.Album(dir))
      .then((album) => {
      // album is json
      /* do some loops and work with the album json*/
      json.music.push(album);
      });
    })
    .then(function(music){
      fs.writeFile('./music.json', JSON.stringify(json), 'utf8', (err)=>{
         if(err) console.log(err)
         else { 
          console.log('File saved');
          }
      })
      return json; // go to the next function if any
    })
    .catch(function(err){
      console.log("Album metadata could not be generated due to some error", err);
    });
  });
};

Above, I have two async functions generateMovieMetaData and generateMusicMetaData each of them has Promise.mapSeries logic

When I call them on their own, they work correctly without throwing errors.

I want to chain the two functions in a composite function like this

var generateMetaData = function(){
  generateMusicMetaData()
   .then(() => generateMovieMetaData());
}
generateMetaData(); 

Calling generateMetaData returns TypeError: Cannot read property 'push' of undefined error from the first function:

Album metadata could not be generated due to some error TypeError: Cannot read property 'push' of undefined
[0]     at musicClient.send.then (/mnt/c/Users/ridhwaan/Source/homehost/server.js:179:23)
[0]     at tryCatcher (/mnt/c/Users/ridhwaan/Source/homehost/node_modules/bluebird/js/release/util.js:16:23)
[0]     at Promise._settlePromiseFromHandler (/mnt/c/Users/ridhwaan/Source/homehost/node_modules/bluebird/js/release/promise.js:51
2:31)
[0]     at Promise._settlePromise (/mnt/c/Users/ridhwaan/Source/homehost/node_modules/bluebird/js/release/promise.js:569:18)
[0]     at Promise._settlePromise0 (/mnt/c/Users/ridhwaan/Source/homehost/node_modules/bluebird/js/release/promise.js:614:10)
[0]     at Promise._settlePromises (/mnt/c/Users/ridhwaan/Source/homehost/node_modules/bluebird/js/release/promise.js:693:18)
[0]     at Async._drainQueue (/mnt/c/Users/ridhwaan/Source/homehost/node_modules/bluebird/js/release/async.js:133:16)
[0]     at Async._drainQueues (/mnt/c/Users/ridhwaan/Source/homehost/node_modules/bluebird/js/release/async.js:143:10)
[0]     at Immediate.Async.drainQueues [as _onImmediate] (/mnt/c/Users/ridhwaan/Source/homehost/node_modules/bluebird/js/release/a
sync.js:17:14)
[0]     at runCallback (timers.js:756:18)
[0]     at tryOnImmediate (timers.js:717:5)
[0]     at processImmediate [as _immediateCallback] (timers.js:697:5)


EDIT 1: Ok I changed the code to look easier. Am getting the same error dunno why
Logging shows that bluebird.mapSeries in both functions is happening at the same time, then throwing the error

I wasnt resolving a promise in each function, I only had a return. I changed the line where I have return json; to resolve(json);

var generateMusicMetaData = function() {
  return new Promise(function(resolve, reject) {
  ...
  r̶e̶t̶u̶r̶n̶ ̶j̶s̶o̶n̶;̶
  resolve(json);
  ...
  });
};

var generateMovieMetaData = function() {
  return new Promise(function(resolve, reject) {
  ...
  r̶e̶t̶u̶r̶n̶ ̶j̶s̶o̶n̶;̶
  resolve(json);
  ...
  });
};

then i have

var generateMetaData = function(){
  generateMusicMetaData()
    .then(function(result) { 
      return generateMovieMetaData();
    });
}

There are a number of problems with the original implementations that make it so they don't return a promise that is resolved when all the async operations in each function are completely done. Because of that, you can't coordinate them with other asynchronous operations.

So first let's fix the implementation of your two functions. It's a lot simpler to just use ES6 async/await for that implementation:

const Promise = require('bluebird');
const fs = Promise.promisifyAll(require('fs'));
const node_dir = Promise.promisifyAll(require('node-dir'));
let moviesClient = new ApiClient(...)
let musicClient = new ApiClient(...)
let lib = require('./index.js');


async function generateMovieMetaData(path) {
    var json = { movies: [] };

    let files = await node_dir.filesAsync(path);

    for (let f of files) {
        let movie = await moviesClient.send(new lib.requests.Movie(f));
        json.movies.push(movie);
    }

    await fs.writeFileAsync('./movies.json', JSON.stringify(json), 'utf8').catch(err => {
        console.log("Movie metadata could not be generated due to some error", err);
        throw err;
    });
    return json;
}

async generateMusicMetaData function(path) {
    var json = { music: [] };

    let files = await node_dir.subdirsAsync(path);

    for (let d of dirs) {
        let album = await musicClient.send(new lib.requests.Album(d));
        json.music.push(album);
    }

    await fs.writeFileAsync('./music.json', JSON.stringify(json), 'utf8').catch(err => {
        console.log("Album metadata could not be generated due to some error", err);
        throw err;
    });
    return json;
}

Note that there are no plain callbacks at all in this implementation. Everything is done using promises and asynchronous operations that don't naturally return promises were "promisified". I used Bluebird here for that since you already showed you were using it, but you could have also used util.promisify() which is built into node.js now.

Now, we have it so each of these functions returns a promise that resolves with the json and onlyh resolves when all the asynchronous operations inside are done. That sets it up well for sequencing using async/await:

function async generateMetaData(path){
    let music = await generateMusicMetaData(path);
    let movie = await generateMovieMetaData(path);
    return {music, movie};
}

generateMetaData(path).then(results => {
    console.log(results);
}).catch(err => {
    console.log(err);
}); 

But, since these appear to be two completely unrelated operations, you could do them in parallel:

function generateMetaData(path){
    return Promise.all([generateMusicMetaData(path), generateMovieMetaData(path)]).then(([music, movie]) => {
        return {music, movie};
    });
}

generateMetaData(path).then(results => {
    console.log(results);
}).catch(err => {
    console.log(err);
}); 

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM