Kenni Showg
Kenni Showg

Reputation: 33

How to loop through/map an array to a function that returns a promise that includes another promise

I've been trying loop through an array of items such that each is used in an operation that returns a promise, but within that promise there's another promise. I'm not getting the desired flow. What am I doing wrong? Or is there a better way. I couldn't use much chaining because of the conditionals. I have tried async/await in several ways still the same undesired result.

The codes have been simplified for clarity. The fetch calls are actually database operations, but the behaviours are still the same; also I've used a single-element array in this case.

var names = ['mike'];
console.log('black');

var fn = function doThings(name) {
  return new Promise((resolve) => {
    console.log('blue');

    var char = name.substr(1);
    getNum(char);
    console.log('violet');

    function getNum(ch) {
      console.log('green');

      fetch('fetchfrom.url')
      .then(response => {
        console.log('orange');

        return response.json();
      })
      .then(n => {

        if(n === 2) {
          console.log('red1');

          fetch('fetchfrom.url')
          .then(response => {
            console.log('yellow');

            return response.json();
          }).then(color => {
            if(n === 2) {
              console.log('red2');

              resolve(5);
            }
            else {
              console.log('brown2');

              resolve(10);
            }
          });
          console.log('lilac');

        } else {
          console.log('brown1');

          resolve(20);
        }

      });
    }

  })


}

var actions = names.map(fn);

Promise.all([actions])
.then(() => {
  console.log('done');
})

I expect the logs to be in the order (assuming n always equals 2): black...blue...green...orange...red1...yellow...red2...lilac...violet...done

But instead i consistently get: black...blue...green...violet...done...orange...red1...yellow...red2...lilac

Upvotes: 2

Views: 98

Answers (2)

robe007
robe007

Reputation: 3927

Another approach on plain promises, is using Promise.all and playing an if-else game with the results:

// These Promises are for ilustrative purposes, but indeed are your fetch's
const promiseA = Promise.resolve(2);
const promiseB = Promise.resolve(2);

// More than one 'name' in the array
const names = ['mike', 'john'];
console.log('black');

const getNum = ch => {
  let response;
  console.log('green');
    return Promise.all([promiseA, promiseB]).then(([PromiseA, PromiseB]) => {
           //^^^^^^^^^^^^^^^^^^^^^^ Resolve all promises to use them now with if-else
    console.log('orange');
    if (PromiseA == 2) {
      console.log('red1');
      console.log('yellow');
      // In your example, you said: if(PromiseA==2). Here I use the second promise
      if (PromiseB == 2) {
        console.log('red2');
        response = 5;
      } else {
        console.log('brown2');
        response = 10;
      }
      console.log('lilac');
    } else {
      console.log('brown1');
      response = 20;
    }
    return response;
  });
};

const doThings = name => {
  console.log(`=>>>>> Evaluating name ${name}`);
  console.log('blue');
  const char = name.substr(1);
  return getNum(char).then(num => {
    console.log('violet');
    return num;
  });
};

// Immediately resolving promise, and then chain new promises
// as the previous ones resolve
let op = Promise.resolve();
const actions = names.map(m => (op = op.then(() => doThings(m))));

Promise.all(actions)
  .then(finalResponse => {
    console.log(`done ${finalResponse}`);
  })
  .catch(e => console.log(`There was an error ${e}`));

This solution avoids using nested then-ables, and your code now looks more readable.

NOTE: The Promise.all solution works semantically with concurrent execution of promises. If the semantical execution is secuentially, take a look here. But in all cases, semantical execution or not, the result will be the same.

Upvotes: 0

Turtlefight
Turtlefight

Reputation: 10710

You need to properly propagate the promises.

  • The new Promise() constructor is not needed in your example, you need to properly call .then() whenever you do something async.
  • when you call fetch() in a .then() handler, return the promise to keep the promise-chain intact.
  • no code after an async action, e.g.: fetch().then();, because the code after that will be executed immediately instead of after the fetch call completed.

You could use async / await, in which your example would look something like this:

var names = ['mike'];
console.log('black');

async function getNum(ch) {
  console.log('green');
  let response = await fetch('fetchfrom.url');
  console.log('orange');
  let n = await response.json();

  if (n === 2) {
    console.log('red1');
    let res = await fetch('fetchfrom.url');
    console.log('yellow');
    let color = await res.json();
    if (n === 2) {
      console.log('red2');
      return 5;
    } else {
      console.log('brown2');
      return 10;
    }
    console.log('lilac');
  } else {
    console.log('brown1');
    return 20;
  }
}

async function doThings(name) {
  console.log('blue');
  var char = name.substr(1);
  let num = await getNum(char);
  console.log('violet');
  return num;
}

var actions = names.map(fn);
Promise.all([actions])
  .then(() => {
    console.log('done');
  });

without async / await & only plain promises you could do:

var names = ['mike'];
console.log('black');

function getNum(ch) {
  console.log('green');
  return fetch('fetchfrom.url').then(response => {
    return response.json();
  }).then(n => {
    console.log('orange');
    if (n === 2) {
      console.log('red1');
      return fetch('fetchfrom.url').then(res => {
        console.log('yellow');
        return res.json();
      }).then(color => {
        if (n === 2) {
          console.log('red2');
          return 5;
        } else {
          console.log('brown2');
          return 10;
        }
      }).then(result => {
        console.log('lilac');
        return result;
      });
    } else {
      console.log('brown1');
      return 20;
    }
  });
}

function doThings(name) {
  console.log('blue');
  var char = name.substr(1);
  return getNum(char).then(num => {
    console.log('violet');
    return num;
  });
}

var actions = names.map(fn);
Promise.all([actions])
  .then(() => {
    console.log('done');
  });

both should result in the expected log order.

Upvotes: 1

Related Questions