exsnake
exsnake

Reputation: 1823

Pause and unpause loop in javascript within an async function

I have an async function that has a loop that I need to be able to pause or unpause it. This is what I have so far.

I use a flag to pause the flow:

let flag = true;

function flag_func() {
    flag = !flag;
}


$(document).ready(function () {
    function sleep(ms) {
        while (!flag) {
            //...waiting.. but infinite loop 
        }
        return new Promise(resolve => setTimeout(resolve, ms));
    }
    async function show_simulation(data) {
        document.getElementById("solve-button").outerHTML = "<button type=\"button\" id='pause-button' onclick='flag_func()' class=\"btn btn-primary btn-lg\">Pause</button>";
        //simulation
        if (data.length === 0) {
            console.log('stuff')
        } else {
            let i;
            for (i = 0; i < data.length; i++) {
                await sleep(40);
                // do stuff
            }
        }
    }
});

The problem is that is being paused, but due the while block the flow, I can't unpause the for loop.

Any idea about how I can solve this?

Upvotes: 2

Views: 1002

Answers (2)

ZER0
ZER0

Reputation: 25322

It might be a nice use case for async iterables. It involves a bit of boilerplate to create your async list, but then the code is much nicer. Basically you would have:

import AsyncList from './async-list.js'

const sleep = (ms) => new Promise(r => setTimeout(r, ms));

async function f(data) {
  const list = new AsyncList(data);

  document.getElementById("btn-toggle").addEventListener("click", function () {
    if (list.paused) {
      this.textContent = "Pause";
      list.resume();
    } else {
      this.textContent = "Resume";
      list.pause()
    }
  })


  for await (let item of list) {
    console.log(item)
    await sleep(1000);
  }
  console.log("end of loop")
}

f([10, "hello", 1029, 90, 80, 209, 44])

A possible implementation of AsyncList could be:

export default class AsyncList {
  constructor(array) {
    // shallow copy
    this._array = array.slice();

    this._index = 0;
    this._length = this._array.length;
    this.paused = false;
    this._resume = () => {}; // noop, in case `resume` is called before `pause`
  }

  [Symbol.asyncIterator]() {
    return this;
  }

  pause() {
    this.paused = true;
  }

  resume() {
    this.paused = false;
    this._resume();
  }

  next() {
    if (this._index < this._length) {
      const value = this._array[this._index++];

      if (this.paused) {
        return new Promise(r => this._resume = r.bind(null, { value }))
      }

      return Promise.resolve({ value })
    } else {
      return Promise.resolve({ done: true });
    }
  }
}

Just to give to you the idea, you could also encapsulate the private properties, and check more scenarios (here I assume data is an array, for example, not just an iterable).

Upvotes: 3

kshetline
kshetline

Reputation: 13682

I'd replace:

    let i;
    for (i = 0; i < data.length; i++) {
        await sleep(40);
        // do stuff
    }

...with...

    let i = 0;
    const doStuff = () => {
      // do stuff

      if (++i < data.length) {
        setTimeout(doStuff, 40);
      }
    };

    setTimeout(doStuff, 40);

Upvotes: 0

Related Questions