Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Recursive promises can cause stack overflow?

For example I found some api library that is based on promises, and I need to issue api requests using this library in some interval, infinite times (like usual back-end loop). This api requests - actually chain of promises.

So, if I write function like:

function r(){
    return api
        .call(api.anotherCall)
        .then(api.anotherCall)
        .then(api.anotherCall)
        ...
        .then(r)
}

Will it cause stack overflow?

Solutions that I come up with is to use setTimeout for a call of r recursively.

function r(){
    return api
        .call(api.anotherCall)
        .then(api.anotherCall)
        .then(api.anotherCall)
        .then(()=>{setTimeout(r, 0)})
}

So setTimeout will call r actually only when call stack is empty.

Is it good solution, or there is some standard way of calling promises recursively?

like image 323
GALIAF95 Avatar asked May 19 '18 04:05

GALIAF95


1 Answers

Will this cause stackoverflow?

No, it will not. Per the promise specification, .then() waits for the stack to completely unwind and is then called after the stack is clear (essentially on the next tick of the event loop). So, .then() is already called asynchronously after the current event is done processing and the stack is unwound. You do not have to use setTimeout() to avoid stack build-up.

Your first code example will not have any stack build-up or stack overflow, no matter how many times you repeat it.

In the Promises/A+ specification, section 2.2.4 says this:

2.2.4 onFulfilled or onRejected must not be called until the execution context stack contains only platform code. [3.1].

And, "platform code" is defined here in 3.1:

“platform code” means engine, environment, and promise implementation code. In practice, this requirement ensures that onFulfilled and onRejected execute asynchronously, after the event loop turn in which then is called, and with a fresh stack. This can be implemented with either a “macro-task” mechanism such as setTimeout or setImmediate, or with a “micro-task” mechanism such as MutationObserver or process.nextTick. Since the promise implementation is considered platform code, it may itself contain a task-scheduling queue or “trampoline” in which the handlers are called.


The ES6 promise specification uses different words, but generates the same effect. In ES6, promise .then() is performed by enqueing a job and then letting that job get processed and the job only gets processed when no other code is running and the stack is empty.

This is how running such as job is described in the ES6 spec:

A Job is an abstract operation that initiates an ECMAScript computation when no other ECMAScript computation is currently in progress. A Job abstract operation may be defined to accept an arbitrary set of job parameters.

Execution of a Job can be initiated only when there is no running execution context and the execution context stack is empty. A PendingJob is a request for the future execution of a Job. A PendingJob is an internal Record whose fields are specified in Table 25. Once execution of a Job is initiated, the Job always executes to completion. No other Job may be initiated until the currently running Job completes. However, the currently running Job or external events may cause the enqueuing of additional PendingJobs that may be initiated sometime after completion of the currently running Job.

like image 188
jfriend00 Avatar answered Oct 17 '22 08:10

jfriend00