Frank D
Frank D

Reputation: 23

javascript, async, await: can't extract value into class variable

tldr How can I set a class variable from within an Promise

I'm banging my head as I'm too stupid to use async/awaitwith javascript. There are of course plenty of examples and blog posts, but they are 'then'inning the results only into console.log, what I don't need.

My use case is very simple, I want to load translations from a json using fetch (if they haven't been loaded yet) and then return the translated value with a function translate.

I thought if I use then, then the execution is paused until the Promiseresolves or fails.

class Test {

    constructor() {
    }

    /**
     * Loads translations
     */
    async _loadTranslations() {
        console.log("Loading tanslations");
        let response = await fetch('data.json');
        let json =  await response.json();
        return json;
    };

    translate(key, language) {
      if(!this.translation){
         this._loadTranslations().then(data =>{
            console.log("data is loaded!", data);
            this.translation = data;});
       }
       return this.translations[language][key];
    }

}

console.log("translation",new Test().translate("MEGA_MENU_CMD","de-DE"))

But it always logs translation undefined.

I don't want something like

  new Test()._loadTranslations().then(r => console.log("result",r))

as I want to use the translate function within templates and I don't want to use .then in all of my templates.

Edit I don't see how I can use the fetch to get data from an API and set it as the model of a class. The thens and callbacks in my opinion do different things. Or how is the correct way to instantiate a class, load data from an API and then work with this data?

2nd edit:

In fact, I just want to create a TranslationMixin and load the translations once. Then in my page (I'm playing around with Polymer) I want to use return html '<div>${this.translate("de-De","propertyX"}<div>' and that's why I don't want a Promise but just the plain string. In fact the translation should be loaded during construction and done. But as fetch returns a Promise I'm stuck with the Promise and fail to get the value out (#sigh). So probably I stick just to regular XMLHttpRequest...

Upvotes: 2

Views: 2109

Answers (4)

Bergi
Bergi

Reputation: 664971

how is the correct way to instantiate a class, load data from an API and then work with this data?

Load data before instantiating the class, then the instance can use it synchronously.

You should load the translations in a static method before creating your instance, so that you won't have to wait for them in your translate method:

class Test {
    constructor(t) {
        this.translations = t;
    }

    /**
     * Loads translations
     */
    static async fromLoadedTranslations() {
        console.log("Loading tanslations");
        let response = await fetch('data.json');
        let json = await response.json();
        console.log("data is loaded!", data);
        return new this(json);
    }

    translate(key, language) {
        return this.translations[language][key];
    }
}

Test.fromLoadedTranslations().then(test => {
    console.log("translation", test.translate("MEGA_MENU_CMD","de-DE"));
    // or pass `test` to your polymer template
});

(async function() {
    const test = await Test.fromLoadedTranslations();
    console.log("translation", test.translate("MEGA_MENU_CMD","de-DE"));
    // or pass `test` to your polymer template
}());

There's no way to load data asynchronously and not wait for it (using either then or await) somewhere.

Upvotes: 0

rmn
rmn

Reputation: 1169

This is the async/await solution you were looking for in the first place.

translate(key, language){
    return this.translation
    ? this.translation[language][key]
    : this._loadTranslations()
}

And since, you need to await the console.log too, and since await can only be inside an async function, you can do the following ..

(async()=>{
    console.log("translation", await new Test().translate("MEGA_MENU_CMD","de-DE"))
})()

Explanation

Function translate returns the result of this._loadTranslations() which is Promise. Then, the console.log statement awaits that Promise and outputs once it is fulfilled, that is, once you have the JSON from data.json.

Upvotes: 0

Tiago Neiva
Tiago Neiva

Reputation: 337

If you want that you must put your translate Asynchronous aswell. So you need to use async and await on that function too.

Because he will actually do what your expect in the _loadTranslations() and he will await for a result, but since the function that is calling it is not async he will do everything till the end! I hope this helps.

Upvotes: 0

Mark
Mark

Reputation: 92450

You need to wait until the translation is finished. The easiest way to do that is to make translate an async function as well. This will return a promise and you can get the translation in the then():

class Test {

    constructor() {
    }

    /**
     * Loads translations
     */
    async _loadTranslations() {
        console.log("Loading tanslations");
        this.translation = "some translation"
        return this.translation;
    };

    async translate(key, language) {
      if(!this.translation){
         return this._loadTranslations().then(data =>{
            console.log("data is loaded!", data);
            return  data;});
       }
       return this.translation;
    }

}
new Test().translate("MEGA_MENU_CMD","de-DE")
.then(translate => console.log(translate))

Upvotes: 2

Related Questions