0

If i figure out how to fix it, im in home. My problem now is to save result from async function. If i do console.log after that function, then i will recive translated value, but few moments later when i checking out how my verbs looks like after packing into object, i will recive non-translated value. Somebody know why?

private translate(file: any) {

    const keys  = Object.keys(file)
    const values = Object.values(file) // [!] - English Value at start, it's ok

    values.forEach( async (value, index) => {
      if( typeof(value) === 'object') {
        value = this.translate(value)           
      } else {
        const translateResult = await this.GetTranslatedByResstring(keys[index], 1045).toPromise()
        value = translateResult.getPhrase() 
        console.log(value)  //  [!] <- Polish Value, it's ok
      }
    })

    const zipObj = xs => ys => xs.reduce( (obj, x, i) => ({ ...obj, [x]: ys[i] }), {})
    const obj = zipObj (keys) (values) 
    console.log(obj) //  [!] <- English Value, it's not ok, i need Polish value inside
    return obj
  }

#Update 1

A place from where calling translate:

public async getTranslatedJson(
    sourceFile: File,
    originLanguage: Language,
    destinatonLanguage: Language
  ): Promise<string> {
    const file = await this.getFile(sourceFile)
    const parsedFile = JSON.parse(file)
    const translatedFile = this.translate(parsedFile)
    return null
  }

#Update 2

My getFile func:

private getFile(
    sourceFile: File
  ): Promise<string> {
    return new Promise((resolve) => {
      const file = sourceFile[0]
      const fileReader = new FileReader();
      fileReader.onloadend = (e) => { 
        const testResult = fileReader.result.toString();
        resolve(testResult);
      }
      fileReader.readAsText(file, "UTF-8");
    });
  }
Obyi
  • 617
  • 1
  • 6
  • 13
  • Ignoring the fact that `.forEach()` doesn't care about an `async` callback... Why is there a `.forEach()` at all? `value` would only ever have the result for the last "value" in `values` – Andreas Mar 24 '21 at 13:02
  • i just need to translate every element in that object, i dont care if it was a foreach, for or other type of loop, i just need to translate them – Obyi Mar 24 '21 at 13:07
  • Nothing in your script even tries to change the content of `values`. Have a look at `Promise.all()` and [How do I return the response from an asynchronous call?](https://stackoverflow.com/questions/14220321/how-do-i-return-the-response-from-an-asynchronous-call) – Andreas Mar 24 '21 at 13:21
  • I trying do anything but im tired of it, i working on this around 1 month. – Obyi Mar 24 '21 at 13:24
  • Perhaps there is no need to convert the observable to promise? How and where is the `translate()` function called? How does it's caller expect the response? – ruth Mar 24 '21 at 13:54
  • I updated @MichaelD Update1 – Obyi Mar 24 '21 at 14:06

1 Answers1

0

Expanding from my comment, I don't see any need to convert the observable to a promise here. You could instead return a RxJS forkJoin function that triggers multiple observables in parallel and subscribe to it where the response is required.

Additionally instead of using Object.keys, Object.values and Array#reduce, you could use Object.entries and Object.fromEntries instead.

Try the following

import { of, forkJoin } from 'rxjs';
import { map } from 'rxjs/operators';

private translate(file: any): Observable<any> {                // <-- return `Observable` here
  return forkJoin(                                             // <-- `forkJoin` to trigger sources in parallel
    Object.entries(file).map(([key, value]) => {               // <-- `Object.entries` with `Array#map`
      if (typeof (value) === 'object')
        return of([key, this.translate(value)]);               // <-- use `of` to convert value to observable
      return this.GetTranslatedByResstring(value, 1045).pipe(
        map((res: any) => ([key, res.getPhrase()]))            // <-- map to the required format [key, value]
      );
    })
  ).pipe(
    map((res: any) => Object.fromEntries(res))                 // <-- convert back to object
  );
}

Now since the function is returning an observable, you need to subscribe to it where it's response is required.

Eg.

ngOnInit() {
  this.translate(file).subscribe({
    next: (value: any) => {
      // any statements that depend on the response must be here
    },
    error: (error: any) => {
      // handle error
    }
  })
}

Update 1

Circling back to my first point, I see no need here to convert the observable to promise. So avoid the toPromise() in the this.getFile() function and return the observable from there. Now in the getTranslatedJson() function you could use a higher order mapping operator like switchMap to map from one observable to another.

public getTranslatedJson(
  sourceFile: File,
  originLanguage: Language,
  destinatonLanguage: Language
): Observable<any> {
  return this.getFile(sourceFile).pipe(
    switchMap((file: any) => 
      this.translate(JSON.parse(file))
    )
  );
}

In this case you would be subscribing to the getTranslatedJson() function where it's response is required.

ngOnInit() {
  this.getTranslatedJson(file, origLang, destLang).subscribe({
    next: (value: any) => {
      // any statements that depend on the response must be here
    },
    error: (error: any) => {
      // handle error
    }
  })
}

Update 2

If for some reason Object.entries and Object.fromEntries are not available, you could always revert back to .values, .keys and Array#reduce like you were initially trying.

private translate(file: any): Observable<any> {                // <-- return `Observable` here
  return forkJoin(                                             // <-- `forkJoin` to trigger sources in parallel
    Object.values(file).map((value: any) => {                  // <-- `Object.values()` with `Array#map`
      if (typeof (value) === 'object')
        return of(this.translate(value));                      // <-- use `of` to convert value to observable
      return this.GetTranslatedByResstring(value, 1045).pipe(
        map((res: any) => res.getPhrase())                     // <-- map to the result from `getPhrase()`
      );
    })
  ).pipe(
    map((res: any) =>                                          // <-- convert back to object
      Object.keys(file).reduce((acc, curr, i) => 
        ({ ...acc, [curr]: res[i] }), 
        Object.create(null)
      );
    )
  );
}

Update 3

You could arrive at most solutions to trivial tasks by adapting previous answers.

// credit: https://stackoverflow.com/a/48802804/6513921

private getFile(
  sourceFile: File
): Observable<any> {
  const file = sourceFile[0];
  const fileReader = new FileReader();

  return Observable.create((observer: Subscriber<any>): void => {
    fileReader.onloadend = ((ev: any): void => {
      observer.next(fileReader.result.toString());
      observer.complete();
    });
    
    fileReader.onerror = (error: any): void => {
      observer.error(error);
    }
  });

  fileReader.readAsText(file, "UTF-8");
}
ruth
  • 29,535
  • 4
  • 30
  • 57
  • How to fix it: Property 'fromEntries' does not exist on type 'ObjectConstructor'. Do you need to change your target library? Try changing the `lib` compiler option. – Obyi Mar 24 '21 at 14:16
  • @Obyi: https://stackoverflow.com/a/45422950/6513921 – ruth Mar 24 '21 at 14:17
  • I added it to webconfig but error still appears, even when i try to fix it in other ways provided in that thread – Obyi Mar 24 '21 at 14:26
  • We touching another function behind my post, so i updated my question, because i can't do pipe and map on my getfile – Obyi Mar 24 '21 at 14:46
  • As i studied your response, i think it not gonna work. Why? I tell you the use case. You uploading JSON file, after this, your JSON was translated by web application, after this JSON need to be ready to download, so the last step making issue, because i subscribe inside service, so i will not have any translations in my component where i downloading my new json file. The simplest way was to BRUTE FORCE that transtlated phrases to my object, but i still dont know how to do it. – Obyi Mar 24 '21 at 15:16