-2

I want to fetch from the backend several data and process them at the end when they're loaded. Right now, when this code runs, data1 and data2 are undefined. I want to wait for them but I don't know how to keep my code clean. I'm not sure Promise.all() will fit here because I need to save the value of data1 and data2 specificaly and I cannot write general code to resolve for a Promise.all().

return new Promise( (resolve,reject) => {
 let data1;
 let data2;

 let url1 = "http://fakeapi.com/getData1";
 fetch(url1)
 .then(res => res.json())
 .then(data => data1SpecificAction())
 .then(data => data1 = data)
 .catch("cannot fetch data1")

 let url2 = "http://fakeapi.com/getData2";
 fetch(url2)
 .then(res => res.json())
 .then(data => data2 = data)
 .catch("cannot fetch data2")

 if(data1 && data2) {
  resolve()
 }
 else {
  reject()
 }
}

How can I fix this snippet?

Olivier D'Ancona
  • 779
  • 2
  • 14
  • 30
  • This questions belongs to https://codereview.stackexchange.com/ – balexandre May 08 '20 at 16:49
  • 1
    Besides other issues, you never assign anything to `data2`... is that a typo? – trincot May 08 '20 at 16:50
  • For starters, any time you're assigning a value to a higher scoped variable from within a `.then()` handler, you are probably doing something wrong. You won't be able to use that higher scoped variable when you want to because ONLY inside the `.then()` handler so you actually know when it's value is valid. – jfriend00 May 08 '20 at 16:51
  • Also, we could help better with real code, not pseudo-code. – jfriend00 May 08 '20 at 16:52
  • 1
    Chain your two promises or use `Promise.all()` or use `await`. Those are your three options. And, get rid of the `new Promise()` you create. No need for that when you already have promises from your async operations from `fetch()`. What you're doing contains several anti-patterns. – jfriend00 May 08 '20 at 16:52
  • What is `data1SpecificAction`? Why do you ignore the first `res` and don't use it at all? – trincot May 08 '20 at 16:55
  • @balexandre - This code does not function properly so it does belong here. – jfriend00 May 08 '20 at 16:57
  • @trincot Yeah it was a typo.@jfriend00 I'm not sure look at this article https://2ality.com/2017/08/promise-callback-data-flow.html – Olivier D'Ancona May 08 '20 at 17:18

2 Answers2

1

You don't need a lot of what you have in your question or your answer. You can just do this:

const url1 = "http://fakeapi.com/getData1";
const url2 = "http://fakeapi.com/getData2";
return Promise.all([
    fetch(url1).then(res => res.json()).then(data1SpecificAction), 
    fetch(url2).then(res => res.json())
]).then([data1, data2] => {
    if (data1 && data2) return;         // resolve
    // reject with appropriate error
    let msg = data1 ? "missing data2" : "missing data1";
    throw new Error(msg);
});

Things you do not need to do in your question and your answer:

  1. Don't wrap existing promises in another manually created promise. That is considered an anti-pattern as the manually created promise is simply not needed. You can just return the promises you already have.
  2. Don't assign a .then() result to a higher scoped variable. Though there are occasionally reasons for this, this is not one of them and is usually a warning sign that you're doing things wrong.

There are some annoyances about the fetch() interface that I find often benefit from a helper function (such as a 404 status resolves, not rejects):

function fetchJson(...args) {
    return fetch(...args).then(res => {
        if (!res.ok) throw new Error(`Got ${res.status} status`);
        return res.json();
    });
}

const url1 = "http://fakeapi.com/getData1";
const url2 = "http://fakeapi.com/getData2";
return Promise.all([
    fetchJson(url1).then(data1SpecificAction), 
    fetchJson(url2)
]).then([data1, data2] => {
    if (data1 && data2) return;         // resolve
    // reject with appropriate error
    let msg = data1 ? "missing data2" : "missing data1";
    throw new Error(msg);
});

And, in your specific case where you want to reject if the result is falsey, you could even do this:

function fetchJsonCheck(...args) {
    return fetch(...args).then(res => {
        if (!res.ok) throw new Error(`Got ${res.status} status`);
        return res.json();
    }).then(result => {
        if (!result) throw new Error("empty result");
        return result;
    });
}

const url1 = "http://fakeapi.com/getData1";
const url2 = "http://fakeapi.com/getData2";
return Promise.all([
    fetchJsonCheck(url1).then(data1SpecificAction), 
    fetchJsonCheck(url2)
]);
jfriend00
  • 683,504
  • 96
  • 985
  • 979
  • 1
    @eelkejohnson - What do you think of these simpler options? – jfriend00 May 08 '20 at 18:42
  • Thanks a lot ! It really helped me go trough. In my real case, I have 4 .then() + 1.catch() for the first action and 2 .then() and 1.catch() for the second action. This way is lighter because you don't need to wrap all action into smaller chunks. But in my case it really improve the readability of my code. Here is my complete code withouth dependency: https://pastebin.com/xPGwx0qg – Olivier D'Ancona May 09 '20 at 06:44
  • To sum up, I learnt what antipattern I was writing and in my case: 1) Improved readability, 2) I didn't knew I could pass data like this :) – Olivier D'Ancona May 09 '20 at 06:50
  • 1
    @EelkeJohnson - FYI, your `exports.controlInstantIncident` function contains a promise anti-pattern. You do NOT need to wrap all that in a promise. Just do return `studentService.getStudentFromHash(...).then(...).then(...).catch(...)`. You are repeating that mistake in lots of other functions. You set the resolved value by what you return from the last `.then()`. You set the reject reason by what you throw from the `.catch()`. No need to wrap in a new promise. That's a pure promise anti-pattern. Return your existing promises. Don't wrap them in new ones. – jfriend00 May 09 '20 at 06:55
  • Right now, I see I programmed my 50k lines of code app like this everywhere :O, and I attend to run my application on a raspberry pi 3 model b with only 500 mb of ram. Will this antipattern I used extensively cause memory issues in the browser ? Because it's generating unnecessary promises? Even my app is running properly, should I spend time on fix all my backend? (I'm short on time) or the difference will be bearly noticeable? thank you for your help – Olivier D'Ancona May 09 '20 at 07:05
  • @EelkeJohnson - It's an anti-pattern not only because it's inefficient, but because most people make mistakes writing code that way and I see several in yours. For example, in `exports.latenessArrivalIncident` you call `resolve()` and `reject()` in three separate promise chains all in the same function. That can't be correct. You should worry the most about the "correctness" of your code (whether it works properly) and the best way to ensure that is to write your promise code properly. – jfriend00 May 09 '20 at 07:11
-1

The solution was to put my specific action into another promises and use promise.all

return new Promise( (resolve,reject) => {
 let data1;
 let data2;

 let promise1 = new Promise( (resolve,reject) => {
  let url1 = "http://fakeapi.com/getData1";
  fetch(url1)
  .then(res => res.json())
  .then(data => data1SpecificAction())
  .then(data => { 
   data1 = data;
   resolve()
   }
  .catch(()=>reject("cannot fetch data1"))
 })

 let promise 2 = new Promise( (resolve,reject) => {
  let url2 = "http://fakeapi.com/getData2";
  fetch(url2)
  .then(res => res.json())
  .then(data => { 
   data2 = data;
   resolve()
   }
  )
  .catch(()=>reject("cannot fetch data2"))
 })

 Promise.all([promise1,promise2])
 .then(
  () => {
   if(data1 && data2) {
    resolve()
   }
   else {
     reject()
   }
  }
 )

here is a link explaining how to pass data between promises.

E_net4
  • 27,810
  • 13
  • 101
  • 139
Olivier D'Ancona
  • 779
  • 2
  • 14
  • 30
  • @trincot how about this? – Olivier D'Ancona May 08 '20 at 17:29
  • 1
    I don't see how that article applies to your situation. That article is about how to share data between promise chains that are part of the same chain - you have two independent promise chains. I've provided an answer that is the most streamlined way I can see to make it work and avoids various anti-patterns. – jfriend00 May 08 '20 at 17:59
  • This is not a good solution as you keep using the promise constructor antipattern. – trincot May 08 '20 at 18:28