How to chain multiple fetch() promises?

老子叫甜甜 提交于 2020-05-26 09:09:34

问题


The following code fetches a json list and then does another fetch call for each list item to change their values. The problem is that it’s not done synchronously. “new” is printed to the console before “update”.

fetch(API_URL_DIARY)
.then(response => response.json())
.then(data => {
  console.log("old", data);
  return data;
})
.then(data => {
  data.forEach(function(e, index,array) {
    fetch(API_URL_FOOD_DETAILS + e.foodid)
    .then(response => response.json())
    .then(data => {
      array[index] = {...e, ...data};
      console.log("update");
    })
  });

  console.log("new", data)
});

Update

Here's how I incorporated @Andy's solution:

function fetchFoodDetails(id, index) {
  return fetch(API_URL_FOOD_DETAILS + id)
  .then(response => response.json())
  .then(data => {
      return [index, data];
  });
}

function fetchDiary() {
  return fetch(API_URL_DIARY)
  .then(response => response.json())
  .then(data => {
    return data;
  })
}

(async () => {
  const data = await fetchDiary();
  console.log("old", JSON.stringify(data));

  const promises = data.map((food, index) => fetchFoodDetails(food.id, index));
  await Promise.all(promises).then(responses => {
    responses.map(response => {
      data[response[0]] = {...data[response[0]], ...response[1]};
      console.log("update");
    })
  });
  console.log('new', JSON.stringify(data));
})();

It was more difficult so I went with @connoraworden's solution. But I think it can be simplified.

Thanks for all your answers.


回答1:


The best way to go about this is to use Promise.all() and map().

What map will do in this context return all the promises from fetch.

Then what will happen is await will make your code execution synchronous as it'll wait for all of the promise to be resolved before continuing to execute.

The problem with using forEach here is that it doesn't wait for asynchronous request to be completed before it moves onto the next item.

The code that you should be using here is:

fetch(API_URL_DIARY)
    .then(response => response.json())
    .then(data => {
        console.log("old", data);
        return data;
    })
    .then(async data => {
        await Promise.all(data.map((e, index, array) => {
            return fetch(API_URL_FOOD_DETAILS + e.foodid)
                .then(response => response.json())
                .then(data => {
                    array[index] = {...e, ...data};
                    console.log("update");
                })
        }));

        console.log("new", data)
    });



回答2:


How to chain multiple fetch() promises?

You do it like how you have been doing it, just append another .then()

fetch(API_URL_DIARY)
.then(response => response.json())
.then(data => {
  console.log("old", data);
  return data;
})
.then(data => {
  data.forEach(function(e, index,array) {
    fetch(API_URL_FOOD_DETAILS + e.foodid)
    .then(response => response.json())
    .then(data => {
      array[index] = {...e, ...data};
      console.log("update");
    })
    .then(()=>{
      console.log("new", data)  
    })
  });
});



回答3:


If you want show only once "console.log("new", data)", you can check it with the index, like this:

fetch(API_URL_DIARY)
    .then(response => response.json())
    .then(data => {
      console.log("old", data);
      return data;
    })
    .then(data => {
      data.forEach(function(e, index,array) {
        fetch(API_URL_FOOD_DETAILS + e.foodid)
        .then(response => response.json())
        .then(data => {
          array[index] = {...e, ...data};
          console.log("update");
           if ((data.length - 1) === index) { // CHECK INDEX HERE IF IS THE LAST
             console.log("new", data)
           }
        })
      });
    });



回答4:


You shouldn't be using forEach here. The best solution is to use Promise.all which waits for an array of promises (fetch is a promise) to all resolve, after which you can process the data.

Here I've created a dummy fetch function with some sample data to quickly show you how that works.

const dummyObj = {
  main: [ { id: 1 }, { id: 2 }, { id: 5 } ],
	other: {
    1: 'data1',
    2: 'data2',
    3: 'data3',
    4: 'data4',
    5: 'data5',
    6: 'data6',
    7: 'data7',
  }  
}

// The summy function simply returns a subset of the sample
// data depending on the type and id params after 2 seconds
// to mimic an API call
function dummyFetch(type, id) {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve(id ? dummyObj[type][id] : dummyObj[type]);
    }, 2000);
  });
}

// In the first fetch we display the data, just
// like you did in your example
dummyFetch('main')
.then(data => {
  console.log("old", data);
  return data;
})
.then(data => {

  // Instead of a forEach use Array.map to iterate over the
  // data and create a new fetch for each
  const promises = data.map(o => dummyFetch('other', o.id));

  // You can then wait for all promises to be resolved
  Promise.all(promises).then((data) => {

    // Here you would iterate over the returned group data
    // (as in your example)
    // I'm just logging the new data as a string
    console.log(JSON.stringify(data));

    // And, finally, there's the new log at the end
    console.log("new", data)
  });
});

Here's the async/await version:

const dummyObj = {
  main: [ { id: 1 }, { id: 2 }, { id: 5 } ],
	other: {
    1: 'data1',
    2: 'data2',
    3: 'data3',
    4: 'data4',
    5: 'data5',
    6: 'data6',
    7: 'data7',
  }  
}

function dummyFetch(type, id) {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve(id ? dummyObj[type][id] : dummyObj[type]);
    }, 2000);
  });
}

(async () => {
  const oldData = await dummyFetch('main');
  console.log("old", oldData);
  const promises = oldData.map(o => dummyFetch('other', o.id));
  const newData = await Promise.all(promises);
  console.log(JSON.stringify(newData));
  console.log('new', newData);
})();



回答5:


fetch is a Promise. This is asyncronous call, so the "new" console.log runs before finished all the promises. Use Promise.all() for that.

You can do this so:

fetch(API_URL_DIARY)
  .then(response => response.json())
  .then(data => {
    console.log("old", data);
    return data;
  })
  .then(data => {
    return Promise.all(data.map(food =>
      fetch(API_URL_FOOD_DETAILS + food.foodid)
        .then(resp => resp.json())
        .then(json => {
          // do some work with json
          return json
        })
    ))
  })
  .then(data => console.log('new', data))



回答6:


You will need a recursive function to do this.

    fetch(API_URL_DIARY)
    .then(response => response.json())
    .then(data => {
      console.log("old", data);
      return data;
    })
    .then(data => {

    recursiveFetch(data)

    });

function recursiveFetch(initialData){
        e = initialData[initialData.length-1]; //taking the last item in array
        fetch(API_URL_FOOD_DETAILS + e.foodid)
        .then(response => response.json())
        .then(data => {
          array[index] = {...e, ...data};
          console.log("update");
          initialData.pop() // removing last item from array, which is already processed
          if(initialData.length > 0)
             recursiveFetch(initialData)
        })
}

Note: This is an untested code.



来源:https://stackoverflow.com/questions/57075049/how-to-chain-multiple-fetch-promises

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!