0

I'm trying to figure out how promises work and I've understood a basic concept but I don`t really get it how should I wait for multiple promises to finish. Especially since they are created dynamically, one after the other.

I'd be happy if someone could point me in a direction for a more elegant solution.

In short, what I need is:

  • Go through an array of objects, (iteration)
  • Check does it exist in DB* (async)
  • If it exists, update data in DB* (async)
  • If it doesn`t exist, add new object to the DB* (async)
  • Once all of this is done, do something else

*I'm using localforage in my app but I'm simulating it with setTimeout.

I've thought of using this approach to sequence iteration: JavaScript ES6 promise for loop

for (let i = 0, p = Promise.resolve(); i < 10; i++) {
    p = p.then(_ => new Promise(resolve =>
        setTimeout(function () {
            console.log(i);
            resolve();
        }, Math.random() * 1000)
    ));
}

It's tricky because I don`t have all the promises created when this iteration completes, so I need to keep track of them (promises) separately and run "Promise.allSettled" only once they are.

Here is my test fiddle https://jsfiddle.net/mefistofelos/2ergotvw/

Entire code here:

let app = {
  "data": {
    "arr" : [5,10,15,20,25,30,35,"1.oni"],
    "ids" : [1,2/*,3,4,5,6,7,8,9,10*/,100],
    "items" : [{"id": 1, "name": "apple"},{"id": 2, "name": "pear"},{"id": 3, "name": "plum"},{"id": 4, "name": "ananas"},{"id": 17, "name": "brick"}],
    getItem : (id, ptIndex) => new Promise(
      resolve => setTimeout(
        () => {
          let resp = {"id": id, "data": [], "ptIndex" : ptIndex};
          resp.data = app.data.items.filter( (ob) => ob.id == id );
          console.log("id: " + id + " ptIndex: " + ptIndex, resp);
          resolve(resp)
        }
      , Math.floor((Math.random() * 1500) + 300))
    ),
    setItem : (data) => new Promise(
      (resolve) => setTimeout(
        () => {
          let resp = data;
          resp.status = "success";
          app.promisesSequence._items.push(data);

          console.log("setItem Resp Data", data);

          return resolve(resp);
        }
      , Math.floor((Math.random() * 2500) + 800))
    )
  },
  // build a set of anonymous functions and run them in sequence
  "promisesSequence": {
    // holds promise functions
    "_items" : [],
    /*"_track" : [],*/
    "_promiseArr" : [],
    // initiate promise tracking
    "track" : {
      "multiplier" : 2,
      "data" : [],
      start : (total) => {
        app.promisesSequence.track.data.push({"total": total,"arr":[]});
        return app.promisesSequence.track.data.length-1;
      },
      // add promise to the promise tracking tree
      addPromise : (ptIndex, promise) => {
        app.promisesSequence.track.data[ptIndex].arr.push(promise);
        return promise;
      },
      isReady : (ptIndex) => (app.promisesSequence.track.getTotal(ptIndex) == app.promisesSequence.track.getTracked(ptIndex) ),
      getTotal : (ptIndex) => app.promisesSequence.track.data[ptIndex].total*app.promisesSequence.track.multiplier,
      getTracked : (ptIndex) => app.promisesSequence.track.data[ptIndex].arr.length,
      getPromises : (ptIndex) => app.promisesSequence.track.data[ptIndex].arr,
    },
    onFinishCb: _ => {
      console.log("[onFinishCb]: Script finished, ("+app.promisesSequence._items.length+") items added: ", app.promisesSequence._items);
    },
    // build promise function array
    build: _ => {
      console.log("init promisesSequence");

      // start tracking items
      let ptIndex = app.promisesSequence.track.start(app.data.ids.length);
      console.log("ptIndex: " + ptIndex);
      // go through array
      for (let i = 0, p = Promise.resolve(); i <= app.data.ids.length-1; i++) {
        console.log("get Item " +app.data.ids[i]+" Data: " );
        p = p.then( _ => {

          app.promisesSequence.track.addPromise(ptIndex,app.data.getItem(app.data.ids[i], ptIndex)).then(
            (resp) => {
              console.log("GOT item (" + resp.id + ") data: ", resp.data);

              if ( resp.data.length >= 1 ) {
                item = resp.data[resp.data.length-1];
                item.edited = true;
                item.a = "edit";
              } else {
                item = {"id": resp.id, "data": [], "name" : "", "a" : "insert"};
              }

              // pass promiseTreeIndex
              item.ptIndex = resp.ptIndex;

              console.log("Try to Set item ("+item.id+"): ",item);

              app.promisesSequence.track.addPromise(item.ptIndex, app.data.setItem(item)).then (
                (resp) => console.log("Item ("+resp.id+") is Set.", resp)
              )

              // try to bind CB after the last of the GET requests have been created
              if ( app.promisesSequence.track.isReady(item.ptIndex) === true ) {
                console.log("**Tracked Promises: " + app.promisesSequence.track.getTracked(item.ptIndex) + " From Total of " + app.promisesSequence.track.getTotal(item.ptIndex));
                console.log("Call Promise.allSettled");
                Promise.allSettled(app.promisesSequence.track.getPromises(item.ptIndex)).then(app.promisesSequence.onFinishCb);
              }

              return i;
            })
        })
      }

      console.log("iteration complete");
    },

    run: () => {
      return app.promisesSequence.build();
    }
  }
}


app.promisesSequence.run();



As you can see, it is sequentially adding Items and I have proper data in both "GET" and "SET" functions, so I've figured out those.

My Question: Is there a more professional way of doing this, than the one I came up with? I was hoping to avoid tracking every promise I create. By going one step further I could have manually checked if they are done so I wouldn`t even need to use .allSettled method.

Goran Usljebrka
  • 853
  • 7
  • 11

1 Answers1

0

await/async could be what I was looking for. This way it's much shorter and does almost the same as I wrote yesterday in the question.

But... the first round of checks is not async... they are not being checked in parallel... I'll need to think about this more, I guess..

let app = {
  "data": {
    "ids" : [1,2,3,4,5,6,7,8,9,10,100],
    "items" : [{"id": 1, "name": "apple"},{"id": 2, "name": "pear"},{"id": 3, "name": "plum"},{"id": 4, "name": "ananas"},{"id": 17, "name": "brick"}],
    getItem : (id) => new Promise(
      resolve => setTimeout(
        () => {
          let resp = {"id": id, "data": []};
          resp.data = app.data.items.filter( (ob) => ob.id == id );
          //console.log("id: " + id , resp);
          resolve(resp)
        }
      , Math.floor((Math.random() * 1500) + 300))
    ),
    setItem : (data) => new Promise(
      (resolve) => setTimeout(
        () => {
          let resp = data;
          resp.status = "success";
          app.promisesSequence._items.push(data);

          //console.log("setItem Resp Data", data);

          return resolve(resp);
        }
      , Math.floor((Math.random() * 2500) + 800))
    )
  },

  // build a set of anonymous functions and run them in sequence
  "promisesSequence": {
    // holds promise functions
    "_items" : [],
    onFinishCb: _ => {
      console.log("Script finished, ("+app.promisesSequence._items.length+") items added: ", app.promisesSequence._items);
    },
    // build promise function array
    build: async _ => {
      console.log("init promisesSequence");
      // go through array
      for (let i = 0; i <= app.data.ids.length-1; i++) {
        console.log("get Item: " +app.data.ids[i] );
        
        var result = await app.data.getItem(app.data.ids[i]);
        console.log( "["+app.data.ids[i]+"] WE HAVE THE ITEM");
        if ( result.data.length == 1 ) {
          item = result.data[result.data.length-1];
          item.a = "edit";
        } else {
          item = {"id": result.id, "data": [], "a" : "insert"};
        }
        console.log("Set item: "+item.id);
        var result = await app.data.setItem(item);
        console.log( "["+app.data.ids[i]+"] ITEM DATA AFTER INSERT: ", result);
      }
      console.log("iteration complete, trigger onFinishCb now");
      app.promisesSequence.onFinishCb();
    },

    run: () => {
      return app.promisesSequence.build();
    }
  }
}

app.promisesSequence.run();

https://jsfiddle.net/mefistofelos/9ocyqbuL/

Goran Usljebrka
  • 853
  • 7
  • 11