How to return the values in the order of the data passed to a promise in a loop?

92 Views Asked by At

To learn about fetch, promise, and other js stuff I'm trying to write a small script that suggests words to learn (base on its difficulty) from a given Japanese text.

It utilizes a Japanese parser called Kuromojin.

What a parser like Kuromojin does is that it tokenizes a phrase into words.

e.g. data: "日本語が上手ですね!" → tokenied words: [{surface_form: 日本語}, {surface_form: が}, {surface_form: 上手}, {surface_form: です}, {surface_form: ね}, {surface_form: !}]

The script first tokenizes words from data, then fetch each tokenized word's corresponding JLPT level by using a Japanese dict's API (jisho.org)

The app.js file:

import {  tokenize,  getTokenizer} from "kuromojin";
import {  parseIntoJisho} from "./parseintojisho.js";

const text = "日本語が上手ですね!";
let tokenSet = new Set();

getTokenizer().then(tokenizer => {});

tokenize(text).then(results => { // the result is an array of objects

  results.forEach((token) => {
    tokenSet.add(token.surface_form);
  })

  console.log("======Begin to find JLPT level for below items======");
  tokenSet.forEach((item) => {   
    console.log(item);

    parseIntoJisho(item); // will throw a console log of whichever item's corresponding data is resolved, regardless of item order here, right?
  });
})

The parseintojisho.js file:

import fetch from 'node-fetch';

/* @param data is string */


export function parseIntoJisho(data) {
  fetch(encodeURI(`https://jisho.org/api/v1/search/words?keyword=${data}`))
    .then(res => res.json())
    .then(jsondata => {
      let JLPTvalueArray = jsondata.data[0].jlpt;

      if (JLPTvalueArray.length) {
        let jlptLevel = JLPTvalueArray.flatMap(str => str.match(/\d+/));
        const max = Math.max(...jlptLevel);
        if (max >= 3) {
          console.log(data + " is of JLPT level N3 or above.")
        } else console.log(data + " is of JLPT level N1 or N2.");
      } else console.log(data + " has no JLPT value.")
    })
    .catch(function(err){
      console.log("No data for " + data);
    })
}

The script works but instead of showing the JLPT level corresponding to each tokenized word in order, it shows randomly. I guess whichever corresponding data is resolved first will appear in the console log?

What I've found out is that Promise.All() may solve my problem, but I couldn't find out a way to implement it correctly.

Is there a way to put the fetched JLPT levels in the order of the tokenized items that were passed into parseIntoJisho(item);?

$ node app.js
======Begin to find JLPT level for below items======
日本語
が
上手
です
ね
!

です has no JLPT value. // should be "日本語 has no JLPT value." here instead
No data for ! // should be "が has no JLPT value." here instead
上手 is of JLPT level N3 or above. 
日本語 has no JLPT value. // should be "です has no JLPT value." here instead
ね is of JLPT level N3 or above.
が has no JLPT value. // should be "No data for !" here instead
1

There are 1 best solutions below

12
CertainPerformance On

Use .map instead of forEach, then log each item of the resulting array after all asynchronous requests are done.

To pass along the original string into the asynchronous results function, use Promise.all on both the parseIntoJisho and the original data.

Promise.all([...tokenSet].map(parseIntoJisho))
  .then((results) => {
    for (const [data, result] of results) {
      if (!result) continue; // there was an error
      if (result.length) {
        const jlptLevel = result.flatMap(str => str.match(/\d+/));
        const max = Math.max(...jlptLevel);
        if (max >= 3) {
          console.log(data + " is of JLPT level N3 or above.")
        } else console.log(data + " is of JLPT level N1 or N2.");
      } else console.log(data + " has no JLPT value.")
    }
  });
const parseIntoJisho = data => Promise.all([
  data,
  fetch(encodeURI(`https://jisho.org/api/v1/search/words?keyword=${data}`))
    .then(res => res.json())
    .then(jsondata => jsondata.data[0].jlpt)
    .catch(err => null)
]);