Run two promises asynchronously, but prioritize the result of the first promise

I'm developing an JS simple project that requires taking the json result (via fetch() or XHR) from 2 services. The first is the main service, and the second acts as a fallback. Each of them can be slower than the other (hint: it's a dictionary app, and 2 service is Wiktionary and Google Translate).

To get a little faster speed, I'm thinking it would be better if I get both of the results asynchronously (in parallel). It still prefer the result of service #1 whether it's fast or slow, and ignore (or abort) the service #2's task.

But if it failed to get the result of service #1, then the result of service #2 will be used as alternative. And because both services are run in parallel (from one point of time), service #2's result can be returned as fast as possible.

Please see my pseudo example here.

const setTimeOutP = (time, label, re = false) => {
  return new Promise((resolve, reject) => {
    setTimeout(function(){
      if(re == false)
        resolve(label);
      else
        reject(label);
    },time);
  });
};

promiseOneMustBeReturnedUnlessReject([setTimeOutP(1000, "promise 1"), setTimeOutP(3000, "promise 2")]); // Promise 1 (in 1s), similar to Promise.race

promiseOneMustBeReturnedUnlessReject([setTimeOutP(3000, "promise 1"), setTimeOutP(1000, "promise 2")]); // Promise 1 (in 3s)

promiseOneMustBeReturnedUnlessReject([setTimeOutP(1000, "promise 1", true), setTimeOutP(3000, "promise 2")]); // Promise 2 (in 3s), NOT 4s

promiseOneMustBeReturnedUnlessReject([setTimeOutP(4000, "promise 1", true), setTimeOutP(2000, "promise 2")]); // Promise 2 (in 4s), NOT 6s

promiseOneMustBeReturnedUnlessReject([setTimeOutP(4000, "promise 1", true), setTimeOutP(2000, "promise 2", true)]); // Reject in 4s

I have a dirty solution right now that I think it would work like I described:

const printResult = async (key) => {
    let dataObj = {
        wiktionary: {
            status: "pending"
        },
        googleTranslate: {
            output: "",
            status: "pending"
        }
    };

    wiktionary(key).then(result => {
        document.getElementById("result").innerHTML = result;
        dataObj.wiktionary.status = "printed";
    }).catch((error) => {
        if (dataObj.googleTranslate.status == "ready") {
            document.getElementById("result").innerHTML = dataObj.googleTranslate.output;
        } else if (dataObj.googleTranslate.status == "error") {
            throw new Error(error);
        } else {
            dataObj.wiktionary.status = "error";
        }
    });

    googleTranslate(key).then(result => {
        if (dataObj.wiktionary.status == "error") {
            document.getElementById("result").innerHTML = result;
            dataObj.googleTranslate.status = "printed";
        } else {
            dataObj.googleTranslate.output = result;
            dataObj.googleTranslate.status = "ready";
        }
    }).catch((error) => {
        if (dataObj.wiktionary.status == "error") {
            throw new Error(error);
        } else {
            dataObj.googleTranslate.status = "error";
        }
    });
};

But is there any more elegant way to handle this situation?

Upvotes: 2

Views: 235

Answers (2)

CertainPerformance
CertainPerformance

Reputation: 371049

You don't have to put .then handlers on promises immediately - you can wait until they're necessary. If the promise has already been resolved by the time you put the new .then on it, the new .then will run immediately.

const printResult = (key) => {
  const googleProm = googleTranslate(key);
  const resultElm = document.getElementById("result");
  wiktionary(key)
    .then(mainResult => resultElm.textContent = mainResult)
    .catch((mainError) => {
      googleProm.then(googleResult => resultElm.textContent = googleResult)
        .catch(googleError => throw new Error(error));
    });
  };

Upvotes: 1

Ry-
Ry-

Reputation: 225125

You can start both promises, then return the fallback in case of error:

const translate = key => {
    const fallback = googleTranslate(key);

    return wiktionary(key)
        .catch(() => fallback);
};

used as follows:

const printResult = async key => {
    const result = await translate(key);
    document.getElementById("result").innerHTML = result;
};

If you have a cancellation mechanism, use it in a .then() before the .catch().

Upvotes: 2

Related Questions