5

I have a dict like this:

{go: ['went', 'run'], love: ['passion', 'like']}

The value of a key is its synonyms. And 'getSynonymWords(word)' is a async function that returns a promise in which Its value is a list of synonym words corresponding with the parameter passed. How can I loop through the object to get another object recursively like this:

{went: [], run: [], passion: [], like: []}

This is my piece of code:

function getRelatedWords(dict) {
  return new Promise(function(resolve) {
    var newDict = {}; 
    for(var key in dict){
      if (dict.hasOwnProperty(key)) {
        var synonyms = dict[key];
        Promise.map(synonyms, function (synonym) {
          return getSynonymWords(synonym).then(function (synonyms) {
            newDict[synonym] = synonyms;
            return newDict;
          }); 
        }).then(function () {
          resolve(newDict);
        }); 
      }   
    }   
  }); 
}

It is incorrect because some tasks are not finished, But I don't know how to run tasks parallel nested with promises. I'm using Bluebird library. Could you help me?

ChrisF
  • 134,786
  • 31
  • 255
  • 325
nguyenngoc101
  • 1,211
  • 4
  • 16
  • 28

1 Answers1

5

First of all, avoid explicit construction. Now that we're over that - we can do this with no nesting and 4 lines of code by first getting all the words, then getting all the synonyms, then folding them back to a dictionary.

function getRelatedWords(dict) {
   // first we get all the synonyms
   var synonyms = Object.keys(dict).map(x => dict[x]).reduce((p, c) => p.concat(c), []);
   // second we get all the synonyms for each word with the word itself
   var withSynonyms = Promise.map(synonyms, s => Promise.all([s, getSynonymWords(s)]));
   // then we fold it back to an object with Promise.reduce
   var asDict = withSynonyms.reduce((p, c) => p[c[0]] = c[1]), {});
   // and return it
   return asDict; 
}

If we wanted to be "clever" we can opt to a one liner, I'm going to use ES2016 here for fun:

let {entries} = Object;
let {reduce, all} = Promise;
const getRelatedWords = dict => reduce(entries(dict), (p, c) => p.concat(c), []).map(s => [s, getSynonymWords(s)]).map(all).reduce((p, [s, syns]) => p[s] = syns, {});

The better solution btw is probably to use something like wordnet that lets you specify the distance and make a single call.

Benjamin Gruenbaum
  • 270,886
  • 87
  • 504
  • 504
  • 1
    Thank you so much. Yours code is so beautiful. But I have a question how to make sure that all `getSynonymWords` are executed before getting `asDict`. – nguyenngoc101 Dec 10 '15 at 10:42
  • @nguyenngoc101 I'm using `Promise.reduce` and not `reduce` which waits for the values if they're promises (that's a bluebird method). I'll edit it to use the not-static version as that's slightly nicer. – Benjamin Gruenbaum Dec 10 '15 at 11:41