1

Here is my object when I log it in Chrome:

{}
de: {path: "/assets/trads/de.json", trads: {…}}
en: {path: "/assets/trads/en.json", trads: {…}}
es: {path: "/assets/trads/es.json", trads: {…}}
fr: {path: "/assets/trads/fr.json", trads: {…}}
it: {path: "/assets/trads/it.json", trads: {…}}
nl: {path: "/assets/trads/nl.json", trads: {…}}
ru: {path: "/assets/trads/ru.json", trads: {…}}
__proto__: Object

Here is it's type when I log it:

Object

If I try:

console.log(obj.en);
console.log(obj['en']);
console.log(obj.length);

I get:

undefined

If I do:

Object.keys(obj).forEach(e => console.log(e));
for (let i in obj) { console.log(obj[i] }
for (let i in obj) { console.log(i) }

I get nothing.

How I made the object from createTradsObjFrom() (ran client-side, getting JSON files that contains translations for some content):

const getJson = path => {
  return new Promise((resolve, reject) => {
    $.getJSON(path, json => {
      if (!json) return reject(new Error('Error while trying to get json for assets trads.'));
      return resolve(json);
    });
  });
};

const createTradsObjFrom = (languages, tradsPaths) => {
  return new Promise((resolve, reject) => {
    try {
      const obj = {};
      languages.forEach(async (lang, index) => {
        const path = tradsPaths[index];
        obj[lang] = {
          path,
          trads: await getJson(path)
        };
      });
      console.log('obj : ', obj);
      return resolve(obj);
    }
    catch (err) {
      return reject(new Error(err));
    }
  });
};

const getTrads = currentLanguage => {
  return new Promise((resolve, reject) => {
    const tradsDir = "/assets/trads/";
    const languages = [
      "de",
      "en",
      "es",
      "fr",
      "it",
      "nl",
      "ru"
    ];
    const tradsPaths = languages.map(e => tradsDir + e + '.json');
    createTradsObjFrom(languages, tradsPaths)
    .then(trads => resolve(trads))
    .catch(err => reject(err));
  });
};

Thanks for your help, I've already spent way too much time on this. :)

  • When you said "Here is my object when I log it in Chrome:" do you mean `console.log(obj)`? The `{}` should enclose those properties and values... how did you "log" it? – nonopolarity Jan 11 '20 at 07:06
  • 1
    Array's `forEach()` doesn't use promises so declaring the callback for it async will not make it wait before iterating to the next item. So when you are resolving the object you are resolving an empty object at that time. You see it in the console as filled because you expand it after the requests are done. Also note you are creating promises for things that already return promises/thenables like `$.getJSON` – Patrick Evans Jan 11 '20 at 07:06
  • Thanks for your help. Yes @nopole, I use console.log(obj), and {} do enclose those properties. PatrickEvans that's a very good point about making promises enclosing other promises like $.getJSON, thanks. For the forEach() point, if I understand it right, it means I can simply remove the "async" and "await" since it does nothing it that case ? Thanks again. –  Jan 11 '20 at 07:16
  • when I `console.log()` an object in Node or Google Chrome, I would see the `{}` enclosing the properties and values: `console.log(obj) // => { en: { path: 'abcd' } }`. How come yours don't, I wonder? – nonopolarity Jan 11 '20 at 07:26
  • @nopole it does, it just did not copy the little arrows on the left. But a simple click on it makes hides it all, and displays it all. –  Jan 11 '20 at 07:28
  • I see, that's how your interactive debugger prints it... then it is strange the debugger prints it as having an `en` property but your `obj.en` is `undefined` – nonopolarity Jan 11 '20 at 07:38

1 Answers1

1

First you are using an anti-pattern of creating promises for calls that already return a promise/thenable. For instance $.getJSON returns a thenable object which you can use with await directly

const getJson = path => {
  return $.getJSON(path);
};

//or just change
trads: await getJson(path)
//to use $.getJSON directly
trads: await $.getJSON(path)

Secondly forEach() doesn't use promises so making it's callback async won't matter as it will not wait for the promise to resolve. You will need to make the createTradsObjFrom itself async and use a normal loop

const createTradsObjFrom = async (languages, tradsPaths) => {
  try {
    const obj = {};
    //set index,lang by destructuring
    for(let [index,lang] of languages.entries()){
      const path = tradsPaths[index];
      obj[lang] = {
        path,
        //just call $.getJSON directly unless needing extra work done
        trads: await $.getJSON(path)
      };
    }
    console.log('obj : ', obj);
    return obj;
  }
  catch (err) {
    //since async functions don't provide a reject method we 
    //need to throw an error to cause a rejection
    throw new Error("some error");
  }
};

Now you can just return createTradsObjFrom() or call your then's directly off it

const getTrads = currentLanguage => {
  //again don't need to make a new promise here as 
  //createTradsObjFrom will be returning one itself
  const tradsDir = "/assets/trads/";
  const languages = ["de","en","es","fr","it","nl","ru"];
  const tradsPaths = languages.map(e => tradsDir + e + '.json');
  return createTradsObjFrom(languages, tradsPaths);
};
getTrads().then(yourObj=>{
  console.log(yourObj);
  //you can now use your filled object
}).catch(err=>{
  console.log("Some error happened: ",err);
});

As to why you are seeing the object as filled in the console log see this Q&A about how the console evaluates the object upon the first expansion of it in the console: Is Chrome's JavaScript console lazy about evaluating arrays?

Patrick Evans
  • 41,991
  • 6
  • 74
  • 87
  • This is awesome Patrick, thank you so much! You helped me, gave me a solution ready-to-go (and working perfectly), and I learned from it a few things that sure will make a difference later. I really appreciate it. –  Jan 11 '20 at 07:47
  • 1
    Rather than process `tradsPaths` synchronously, as you have done here, I would instead use `map()` to make it return an array of "getJSON" promises, then wait on them all and then build the object. Something similar to `tradsData = await Promise.all(tradsPaths.map(p => $.getJSON(p)))` (not sure what the jQuery equivalent of `Promise.all` is). – samthecodingman Jan 11 '20 at 07:55
  • @samthecodingman one could certainly could do that, and actually thought about showing that way. But such an optimization would just be a distraction to answering the actual problem at hand in my opinion as it would also require an explanation itself as to not confuse OP or future users. So just went with the original problem's flow. – Patrick Evans Jan 11 '20 at 08:06
  • 1
    Side note, the jQuery equivalent would be `$.when(...arrayOfPromises)` as `when()` takes each promise as an argument instead of a single argument of an array like `Promise.all()`. – Patrick Evans Jan 11 '20 at 08:12