0

Suppose I have a web application returning exchange rate for given currency pair at /exchange_rate/ccy1/ccy2, whenever the currency pair is not found in the database, the web app returns 404.

In the front-end JavaScript code, to be fail-safe, whenever local exchange rate query fails, I'll then query a public provider, say currencyconverterapi.com

Let's also assume for some reason (the actual code uses Vue lifecycle hooks), the global variable rate, and the two functions can't be merged and can't be made into async.

The problem with the following code is it only works when local query axios.get('/exchange_rate/EUR/' + ccy) resolves; when it rejects, the amount * rate will execute before axios.get('http://free.currencyconverterapi.com...) sets rate --

var rate;

function getExchangeRate(ccy) {
    return axios.get('/exchange_rate/EUR/' + ccy)
    .then(ret => {
        rate = ret.data.rate;
    }).catch(err => {
        axios.get('http://free.currencyconverterapi.com/api/v5/convert?q=EUR_' 
        + ccy).then(ret => {
            rate = ret.data.results.val;
        });
    });
}

function toEUR(ccy, amount) {
    getExchangeRate(ccy)
    .then(() => {
        return amount * rate;
    });
}

var EURAmount = toEUR('USD', 42);

My question is: is there a way to guarantee rate is properly set by getExchangeRate in toEUR?

Jerry Ji
  • 386
  • 1
  • 3
  • 15
  • You could check the value of rate in the .then() of toEUR() and if it is not set correctly, do a recursive call of toEUR(), else return. – Ryan Wilson Jun 14 '18 at 15:54
  • 2
    `toEUR()` doesn't return your promise. There are a few bad practices in your code, e.g. nesting a promise in another promise, and using a global updated asynchronously. The two functions don't need to be merged, just chained properly with `.then()`. – Patrick Roberts Jun 14 '18 at 15:55
  • Have `toEUR` check if `rate===undefined`, and return an empty value if so. Assuming toEUR is a Vue computed value, and `rate` is a data field on the component, it'll automatically run again when `rate` changes. – Daniel Beck Jun 14 '18 at 15:55
  • **...the two functions can't be merged and can't be made into async can't be made into async** - then perhaps the return value should't depend on a value that can only be returned via an asynchronous call? The answer to this question is just like all the similar questions - change the way your code works so it's asynchronous, that, and as @PatrickRoberts said, you need to return the Promise from `toEUR` - `return getExchangeRate(ccy)...` – Adam Jenkins Jun 14 '18 at 15:59
  • Thanks everyone for your valuable input. Just wish to clarify more on the seemingly convoluted use of global variable and the two functions for any future visitors -- the snippet here is not a literal copy of the actual code, which uses Vue. "rate" is a Vue data variable, getExchangeRate() is one of the Vue methods, and toEUR() is a beforeMount hook function – Jerry Ji Jun 15 '18 at 03:45

1 Answers1

3

The then in your toEUR function is not waiting for the second request to complete because you are not returning in the catch.

You should also not be using shared state for the rate variable. Just return it as the result of your promise.

function getExchangeRate(ccy) {
    return axios.get('/exchange_rate/EUR/' + ccy)
    .then(ret => ret.data.rate)
    .catch(err => {
        return axios.get('http://free.currencyconverterapi.com/api/v5/convert?q=EUR_' 
        + ccy)
            .then(ret => ret.data.results.val);
    });
}

function toEUR(ccy, amount) {
    return getExchangeRate(ccy)
        .then(rate => amount * rate);
}

toEUR('USD', 42)
    .then(amount => console.log(amount))
    .catch(err => console.error(err));

I would advise factoring out the backup call as a separate function so you don't have nested promises:

function getExchangeRatePublicProvider(ccy) {
    return axios.get('http://free.currencyconverterapi.com/api/v5/convert?q=EUR_' 
        + ccy)
        .then(ret => ret.data.results.val);
}

function getExchangeRate(ccy) {
    return axios.get('/exchange_rate/EUR/' + ccy)
        .then(ret => ret.data.rate)
        .catch(err => getExhangeRatePublicProvider(ccy));
}

function toEUR(ccy, amount) {
    return getExchangeRate(ccy)
        .then(rate => amount * rate);
}

toEUR('USD', 42)
    .then(amount => console.log(amount))
    .catch(err => console.error(err));
JLRishe
  • 99,490
  • 19
  • 131
  • 169
  • Perfect answer. This covers all the issues I mentioned in [my comment](https://stackoverflow.com/questions/50861492/javascript-asynchronous-function-woe#comment88725570_50861492) and then some. – Patrick Roberts Jun 14 '18 at 16:03