e_netr
e_netr

Reputation: 592

How can I run promises in sequence and dynamically

In my web application I can run blocks of code (it creates a promise and waits for the result to come out). Every time a user runs a paragraph, I add it's id to an array and run it sequentially.

runSequentially(paragraphsId) {
    paragraphsId.reduce((promise, paragraphId) => {
        return promise.then(() => this.runParagraph(paragraphId))
    }, Promise.resolve())
}

addToQueue(paragraphId) {
    if (this.state.runQueue.indexOf(paragraphId) === -1) {
        this.setState({
            runQueue: [...this.state.runQueue, paragraphId]
        }, () => this.runSequentially(this.state.runQueue))
    }
}

runParagraph(paragraphId) {
    const newParagraphResults = { ...this.state.paragraphResults }
    delete newParagraphResults[paragraphId]

    const newParagraphs = { ...this.state.paragraphs }
    const newParagraph = newParagraphs[paragraphId]
    newParagraph.isRunning = true
    newParagraph.status = 'running'
    this.setState({
        paragraphs: newParagraphs,
        paragraphResults: newParagraphResults
    })

    const paragraphs = [
        {
            identifiers: { id: paragraphId },
            title: newParagraph.title,
            source: newParagraph.source
        }
    ]

    const notebookLibraries = Object.values(this.state.notebookLibraries)

    this.runController = new AbortController()
    return this.service.notebookRun(this.notebookId, paragraphs, notebookLibraries, this.runController)
        .then(result => {
            Object.entries(result.paragraphs).forEach(entry => {
                if (entry[0] === 'default_paragraph') {
                    return
                }
                const paragraphId = entry[0]
                const paragraphResult = entry[1]
                newParagraphResults[paragraphId] = paragraphResult
                paragraphResult.exception ? this.setParagraph(paragraphId, { status: 'failed' }) :
                    this.setParagraph(paragraphId, { status: 'passed' })
            })
            this.setState({ paragraphResults: newParagraphResults })
        })
        .catch((error) => {
            if (error.name === 'AbortError') {
                return Promise.reject(error)
            }
            const message = `Execution failed for reason: ${error.reason}.`
            this.handleServiceError('notebook', 'run', message)
        })
        .finally(() => {
            const newRunQueue = [ ...this.state.runQueue ]
            newRunQueue.shift()
            this.setParagraph(paragraphId, { isRunning: false })
            this.setState({ runQueue: newRunQueue })
        })
}

When a user runs a paragraph we call addToQueue which then calls runSequentially. We shift the queue when a promise is settled (in the runParagraph method) but if we run another paragraph before the first one has finished this will iterate over the same promise twice.

How would you handle this dynamic queue of promises ? Could recursivity work in this case ?

Upvotes: 0

Views: 392

Answers (2)

Bergi
Bergi

Reputation: 664297

if we run another paragraph before the first one has finished this will iterate over the same promise twice.

You will need to keep the promise queue as a property in your state, instead of creating a new Promise.resolve() every time you call runSequentially. See here for an example implementation.

If you want to manage your queue strictly through setState, you should not need a runSequentially method at all. runParagraph itself would a) check whether it already is running and b) when finished dequeue the next element from the array and call runParagraph again until there a none left.

Upvotes: 2

Patrick Roberts
Patrick Roberts

Reputation: 51816

You should initialize another property (perhaps queue is not the best name since you already have state.runQueue) in your class to Promise.resolve(), and let that be the pending promise of your sequential queue. Then you can do something like this:

runSequentially(...paragraphsId) {
  this.queue = paragraphsId.reduce((promise, paragraphId) => {
    return promise.then(() => this.runParagraph(paragraphId))
  }, this.queue)
}

addToQueue(paragraphId) {
  if (this.state.runQueue.indexOf(paragraphId) === -1) {
    this.setState({
      runQueue: [...this.state.runQueue, paragraphId]
    }, () => this.runSequentially(paragraphId))
  }
}

runSequentially() now accepts incremental updates rather than the entire runQueue, and you shouldn't store queue on your state variable because the promise itself doesn't affect your render, so it's safe.

Upvotes: 2

Related Questions