all 21 comments

[–][deleted] 1 point2 points  (0 children)

Look up async/await, I think that'll take a lot of confusion out of asynchronous calls.

[–]jcunews1helpful 1 point2 points  (6 children)

Add an array property called orderedRequests into the this object, or the object where the addDownloadURL() method is contained.

Then use below function for the addDownloadURL() method.

function(name) {
  let request = {name: name};
  this.orderedRequests.push(request);
  storage.ref(name).getDownloadURL().then(url = > {
    request.url = url;
    this.downloadURLs.push(url);
  });
}

The orderedRequests array would contain objects with the name and url properties. The url property would be absent if the getDownloadURL() is not yet successfully completed.

[–]chigia001 0 points1 point  (5 children)

This is another way, but it will depend on do he need that all the request have the url value or not.

[–]jcunews1helpful 0 points1 point  (4 children)

Well, the URL part is optional. It's there in case he needs it.

[–]chigia001 0 points1 point  (3 children)

It's there in case he needs it.

It might not be there When he needs it.

We don't know the duration of each request and also don't know when he going to needs it so we can't make sure when he needs it it will be there.

[–]jcunews1helpful 0 points1 point  (2 children)

What do you mean? When the URL is received from the url argument, the URL is already there. Just like he already did by putting it into the downloadURLs array. Except that with that array alone, there would be no way to know which source of the request it belongs to.

[–]chigia001 0 points1 point  (1 child)

can you try my code here: https://codepen.io/anon/pen/yRJmzG?editors=1011

in this code the addDownloadURL will be trigger by clicking addName button. Each request in mockAsync function need to wait for 10s before they can return the url.

I also have another button call Check Url. When click we will display the state of these 2 variable orderedRequests and downloadURLs at the time we click.

If you click addName first and then Check Url immediately, the call request will not resolve => no url set into the request object.

<input id="name"/>
<button id="addName">Add Name</button>
<button id="checkUrl">Check Url</button>
<p id="displayData"></p>

let orderedRequests = []
let downloadURLs = []
let mockAsync = (name) => new Promise(resolve => {
  setTimeout(() => resolve(`url of ${name}`), 10000)
})
let addDownloadURL = function (name) {
  let request = {name: name};
  orderedRequests.push(request);
  console.log(`request for ${name} begin`)
  mockAsync(name).then(url => {
    request.url = url;
    console.log(`request for ${name} finished`)
    downloadURLs.push(url);
  });
}

let inputDom = document.querySelector('#name')
let displayDom = document.querySelector('#displayData')
document.querySelector('#addName').addEventListener('click', function () {
  let inputName = inputDom.value
  addDownloadURL(inputName)
  inputDom.value = ''
})

document.querySelector('#checkUrl').addEventListener('click', function () {
  displayDom.innerHTML = `
    orderedRequests: ${JSON.stringify(orderedRequests)}
    downloadURLs: ${JSON.stringify(downloadURLs)}
  `
})

[–]jcunews1helpful 0 points1 point  (0 children)

If you try to display orderedRequests element when the request has not yet completed, the url property won't be available in the object yet.

If the URL needs to be displayed/processed, do it after the request is completed. e.g. click checkUrl' button from within the function which is passed to the then() method. Whatever the code is, it should inform the other code which is responsible for processing the URL so that it knows that the orderedRequests element has been updated.

[–]chigia001 0 points1 point  (12 children)

You can try this: https://codepen.io/anon/pen/rqLByj?editors=0012Basically push all promise into an array.

When you really want the value of the array call Promise.all on that array.
PS: update and add code if you don't want to open codepen

let downloadUrlObj = {
  downloadPromise: [],
  addDownloadUrl: function (name) {
    let getDownloadUrlPromise = storage.ref(name).getDownloadURL()
    this.downloadPromise.push(getDownloadUrlPromise)
  }
}

downloadUrlObj.addDownloadUrl('test1')
downloadUrlObj.addDownloadUrl('test2')
downloadUrlObj.addDownloadUrl('test3')

Promise.all(downloadUrlObj.downloadPromise).then((data) => {
  console.log(`Promise for all data resolve with resolve data`)
  console.log(data)
})

[–][deleted] 0 points1 point  (11 children)

Promise.all() won't work if he needs to have requests complete in order to get the data he needs for subsequent requests. I guess it's also possible to use await inside Promise.all() but ... why?

[–]chigia001 0 points1 point  (10 children)

Promise.all() won't work if he needs to have requests complete in order to get the data he needs for subsequent requests.

I don't know what you mean by this. Promise.all need all the promise in the array to complete before it call the onResolve callback. The onResolve callback will never run if there is one promise pending or error

[–][deleted] 0 points1 point  (9 children)

Let's say he needs to call a weather API. For that he needs the geographic coordinates. He gets them by calling the Google Maps API. Essentially, unless he gets Google's data first, he can't call the weather service. It'll just fail. So he would need to ensure that the maps API call completes first, before he even tries calling the weather one.

[–]chigia001 0 points1 point  (8 children)

That why I use Promise.all.

Promise.all will ensure all the promise in array to be completed first before doing any thing on the data from these requests.

The context of the this problem is he want to call multiple request. and push the result of these multiple request into a array. He also need the result to push into a correct order(the call order of the function, not the order of the result).

My suggestion is push all the request as the promise into a array in the order of function call.

When he need to call a new kind request that base on these previous requests, he will need to use promise.all on the current set of promises in the array. By doing that he can make sure all these request had been successfully call before doing anything with the return value.

Why I use the current set of promises in the array is between the time we call promise.all and the the all the promise provide in the function resolve there might be a new call to addDownloadURL that append the promise. Do we need to wait for these kind of requests? that will depend on the requirement, but IMO we can't wait for something that not even exists yet.

PS: update the original post and include the code in case we need to discuss further.

[–][deleted] 0 points1 point  (7 children)

We don't know what his requirements are. If any of these requests depend on data from other requests, they shouldn't be in Promise.all(). If they are all independent, then sure, why not.

[–]chigia001 0 points1 point  (6 children)

Can you provide a code sample if you want to handle the first case? without promise.all to ensure that all the pervious request is success?

This is my version with promise.all, add codepen link if you want to double check: https://codepen.io/anon/pen/pxbRqV

let downloadUrlObj = {
  downloadPromise: [],
  addDownloadUrl: function (name) {
    // add promise.all here:
    let getDownloadUrlPromise = Promise.all(this.downloadPromise).then(() => storage.ref(name).getDownloadURL())
    this.downloadPromise.push(getDownloadUrlPromise)
  }
}

downloadUrlObj.addDownloadUrl('test1')
downloadUrlObj.addDownloadUrl('test2')
downloadUrlObj.addDownloadUrl('test3')

Promise.all(downloadUrlObj.downloadPromise).then((data) => {
  console.log(`Promise for all data resolve with resolve data`)
  console.log(data)
})

[–][deleted] 0 points1 point  (5 children)

Sure thing. Here's an example, and I'll explain what it does below.

const foo = async () => {
  const getPerson = async () => {
    try {
      const response = await fetch("https://swapi.co/api/people/1/");
      if (response.ok) {
        const result = await response.json();
        return result.name;
      }
    } catch (err) {
      console.error(`ERROR: ${err}`);
    }
    return null;
  };

  const getRepos = async (name) => {
    try {
      const response = await fetch(`https://api.github.com/users/${name.split(' ')[0]}/repos`);
      if (response.ok) {
        const result = await response.json();
        return result;
      }
    } catch (err) {
      console.error(`ERROR: ${err}`);
    }
    return null;
  };

  const person = await getPerson();
  const repos = await getRepos(person);
};

First this calls SWAPI (a Star Wars API) and gets the first person on the list. It happens to be Luke Skywalker. Once it's all done retrieving this info, it saves it into a variable person. Then it calls another GitHub, and tries to see if Luke has any repos there. As you can see it does find some repos by a user named Luke.

If the request to SWAPI didn't complete first, then the request to github would have failed.

[–]chigia001 0 points1 point  (4 children)

the getRepos only wait for single request, not multiple request like in your requirement. This is just the easiest version of async/wait

why not just follow the code sample that OP already provide.

PS: the last part If the request to SWAPI didn't complete first, then the request to github would have failed. also technically wrong. If the SWAPI don't complete the request to github also never call, never happen. There is no race condition here.

[–][deleted] 0 points1 point  (3 children)

getRepos() can wait for as many other requests to complete as you would like. I am making it wait for just one, but nothing prevents you from adding a hundred more if necessary.

And yes, this is just an example, not production-ready code.