0
function sleep(t) {
    return new Promise((resolve, reject) =>{
        setTimeout(() => {
            console.log('timeout!')
            return resolve({isTimeout: true})
        }, t);
    });
}

function thirdPartyFunction(t) { // thirdPartyFunction can't be edited
    return new Promise((resolve, reject) =>{
        setTimeout(() => {
            console.log('thirdPartyFunction completed!')
            return resolve({success: true})
        }, t);
    });
}

function main() {
    return new Promise(async(resolve, reject) => {
        try {
            let thirdPartyFunctionExecutionTime =  Math.floor(Math.random() * 10) + 1;
            thirdPartyFunction(thirdPartyFunctionExecutionTime * 1000, false).then( (r) => {
                console.log('should not execute this if thirdPartyFunctionExecutionTime > timeout') // only goal
                // other code which is not useful after timeout
            });

            const timeout = 3;
            console.log(`thirdPartyFunctionExecutionTime: ${thirdPartyFunctionExecutionTime}, timeout - ${timeout}`)
            await sleep(timeout * 1000, true);
            throw 'stop main()'
            // return
        } catch (error) {
            console.log('in catch')
            return;
        }
    })
}


main()

Timeout is fixed. thirdPartyFunctionExecutionTime might be very large (sometimes) in my actual case, say 30 secs. I don't want something to be running on background after timeout.

thirdPartyFunction promise function should stop execution on timeout.

Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • 2
    Unless your `thirdPartyFunction()` has an explicit way of cancelling and stopping it's operation, there's nothing you can do. It will go about its business and complete whenever it completes regardless of your timeout implementation. – jfriend00 Jan 10 '23 at 19:35
  • FYI, this timeout implementation looks flawed and incomplete in a bunch of ways - for example, you don't call `resolve()` or `reject()` on the wrapper promise in `main()`. Typically a timeout is added to an existing promise using `Promise.race()` where its a race between the timeout and the actual asynchronous operation. Whichever finishes first is the result the caller sees. When the asynchronous operation finishes, it cancels the timer. When the timer fires, it cancels the asynchronous operation (if the async operation has a cancel method). – jfriend00 Jan 10 '23 at 19:37
  • FYI, you can avoid executing the body of the `.then()` by just setting a local flag if you timeout and checking that flag in the `.then()`. But, I wouldn't write a timeout this way at all. – jfriend00 Jan 10 '23 at 19:40
  • 1
    If `thirdPartyFunction` is indeed based on a `setTimeout` call, then you can cancel the time out. However, I assume your actual `thirdPartyFunction` is another asynchronous request (like HTTP request). Such requests are implemented in non-JavaScript code. You cannot hope to *abort* non-JavaScript code with JavaScript. All you can do is ignore the event that tells you the promise has eventually resolved. – trincot Jan 10 '23 at 19:45
  • 1
    [Never pass an `async function` as the executor to `new Promise`](https://stackoverflow.com/q/43036229/1048572)! – Bergi Jan 10 '23 at 20:46

1 Answers1

0

FYI, here's a generic function to add a timeout with the option of cancelling. This can be used with any promise to add a timeout to it.

If the underlying asynchronous operation represented by the promise is cancellable, then you can supply a cancel function. If the async operation finishes first, this will clean up the timer for you automatically so it doesn't keep running.

// the main purpose of this class is so that callers can test
// to see if the reject reason is a TimeoutError vs. some other type of error
class TimeoutError extends Error {
    constructor(...args) {
        super(...args);
    }

}

// add a timeout to any promise
function addTimeout(promise, t, timeoutMsg = "timeout") {
    let timer;
    const timerPromise = new Promise((resolve, reject) => {
        timer = setTimeout(() => {
            timer = null;
            // if the promise has a .cancel() method, then call it
            if (typeof promise.cancel === "function") {
                try {
                    promise.cancel();
                } catch(e) {
                    console.log(e);
                }
            }
            reject(new TimeoutError(timeoutMsg));
        }, t)
    });
    // make sure the timer doesn't keep running if the promise finished first
    promise.finally(() => {
        if (timer) {
            clearTimeout(timer);
        }
    })
    return Promise.race([promise, timerPromise]);
}

And, you could then use this in your code like this:

function main() {
    addTimeout(thirdPartyFunction(...), 3000).then(result => {
        // thirdPartyFunction succeeded and timeout was not hit
        // use the result here
    }).catch(err => {
        console.log(err);
        // an error occurred here
        if (err instanceof TimeoutError) {
            // timeout
        } else {
            // other error
        }
    })
}

If your asynchronous operation has an ability to be cancelled if this operation times out, you could support that like this:

function main() {
    let p = thirdPartyFunction(...);
    p.cancel = function () {
        // cancel the thirdPartyFunction here (depends uponn the specific operation)
    }
    addTimeout(p, 3000).then(result => {
        // thirdPartyFunction succeeded and timeout was not hit
        // use the result here
    }).catch(err => {
        console.log(err);
        // an error occurred here
        if (err instanceof TimeoutError) {
            // timeout
        } else {
            // other error
        }
    })
}
jfriend00
  • 683,504
  • 96
  • 985
  • 979