Can't add timeout to a heavy promise with Promise.race

305 Views Asked by At

I'm working on mobile app development using React Native and Expo. I have a promise which calls GeoFirestore to query data from firebase firestore by distance order, and my app sometimes crashes because the GeoFirestore query doesn't finish.

So I need to add a timeout to the heavy promise, and I tried to use Promise.race with two promises, a promise that calls GeoFirestore API, and another promise that rejects after a given time using SetTimeout, like the below code (put infinite loop instead of actual GeoFirestore call so that it is executable).

However, I found that setTimeout doesn't reject even if the time passed longer than the given time.

How can we add a timeout to a heavy promise?

I guess that javascript runs on a single-thread and the heavy operation takes a thread but the timer task couldn't run...

const makeTimeoutPromise = (timeMs) => new Promise(function(resolve, reject) {
  setTimeout(() => reject(`timeout`), timeMs);
});

const heavyOperationPromise = () => new Promise(function(resolve, reject) {
  while (true) { } // a very heayy operation. 
  resolve();
});

const test = async () => {
  try {
    console.log('test started');
    const result = await Promise.race([makeTimeoutPromise(100), heavyOperationPromise()]);
    console.log(`done result=${result}`);
  } catch (e) {
    console.log(`catch e=${e}`);
  }
}

test();

Update:

while(true) {} seems not good example, which blocks a thread.

Here is actual code. GeoQuery.near is what I need to run in a promise and it takes too long or let app crash depending on center, radius and data in firestore DB.

import { GeoFirestore } from 'geofirestore'; // GeoFirestore 3.4.1

getUsersByDistance = async (location, radius) => {
  try {
    const geofirestore = new GeoFirestore(firebase.firestore());
    const activeUsers = geofirestore.collection('users').where('userStatus', '==', 'active');
    const query = activeUsers.near({
      center: new firebase.firestore.GeoPoint(location.latitude, location.longitude), radius });

    const makeTimeoutPromise = (timeMs) => new Promise(function(resolve, reject) {
      setTimeout(() => reject('timeout'), timeMs);
    });

    // set timeout 5 seconds, but the setTimeout doesn't reject sometimes depending on the center, radius and data in firestore DB.
    // query.get sometimes takes very long time, sometimes app crash. 
    const result = await Promise.race([makeTimeoutPromise(5000), query.get()]);
    // Do something with result
  } catch (e) {
    console.log(e);
  }
}
1

There are 1 best solutions below

0
On

If the heavy operation is not async it won't give a chance for the setTimeout callback to execute from the event loop. If given a breathing space it will work for example.

function sleep(ms) {
    return new Promise((resolve) => setTimeout(resolve, ms));
}

const makeTimeoutPromise = (timeMs) => new Promise(function(resolve, reject) {
  setTimeout(() => reject(`timeout`), timeMs);
});

const heavyOperationPromise = () => new Promise(async function(resolve, reject) {
  while (true) {
    await sleep(1);
  } // a very heayy operation. 
  resolve();
});

const test = async () => {
  try {
    console.log('test started');
    const result = await Promise.race([makeTimeoutPromise(100), heavyOperationPromise()]);
    console.log(`done result=${result}`);
  } catch (e) {
    console.log(`catch e=${e}`);
  }
}

test();