Newbie
Newbie

Reputation: 373

javascript - cancel awaited function before it is called again

On incoming webrtc call, I open a modal to show user a message about media permissions and await this till the user presses OK button on the modal.

I do it like this:

async function myMessage(){
    $('#id_my_message_modal').modal('show');
    return new Promise(resolve => 
        $('#id_my_message_button').on('click', () => {
                $('#id_my_message_modal').modal('hide');
                resolve();
            }
        )
    );
}

then

await myMessage();

The issue I am facing now is if await myMessage(); is called again while the previous call has still not returned(i.e user hasn't pressed OK button). I want a way to cancel any previous await myMessage();, if exists, before it is called again.

Is there any way to do it?

Upvotes: 0

Views: 301

Answers (1)

Dmitriy Mozgovoy
Dmitriy Mozgovoy

Reputation: 1597

The first approach (Live demo)- add every call of the async function to the queue, so you will get a sequence of dialogs with the result returning (close/accept/whatever).

// decorator from my other answer
function asyncBottleneck(fn, concurrency = 1) {
  const queue = [];
  let pending = 0;
  return async (...args) => {
    if (pending === concurrency) {
      await new Promise((resolve) => queue.push(resolve));
    }

    pending++;

    return fn(...args).then((value) => {
      pending--;
      queue.length && queue.shift()();
      return value;
    });
  };
}

const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));

const myMessage = asyncBottleneck(async function () {
  const $modal = $("#id_my_message_modal");
  $modal.modal("show");
  const result = await new Promise((resolve) =>
    $modal.on("click", "button", (e) => {
      $modal.modal("hide");
      $modal.off("click", "button");
      resolve($(e.target).data("action"));
    })
  );
  await delay(250);
  return result;
});

The second approach (Live demo)- multiplexing the fn calls, when every await of the function will return the same result; Compare console output of the live demos.

function singleThread(fn) {
  let promise = null;

  return function (...args) {
    return (
      promise ||
      (promise = fn(...args).finally(() => {
        promise = null;
      }))
    );
  };
}

const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));

const myMessage = singleThread(async function () {
  const $modal = $("#id_my_message_modal");
  $modal.modal("show");
  const result = await new Promise((resolve) =>
    $modal.on("click", "button", (e) => {
      $modal.modal("hide");
      $modal.off("click", "button");
      resolve($(e.target).data("action"));
    })
  );
  await delay(250);
  return result;
});

$("#btn-run").on("click", async () => {
  myMessage().then((c) => console.log(`First result: ${c}`));
  await myMessage().then((c) => console.log(`Second result: ${c}`));

  myMessage().then((c) => console.log(`Third result: ${c}`));
});

The third way- closing the previous modal with its promise rejecting (Live Demo open console there to see the result).

import CPromise from "c-promise2";

const showModal = (() => {
  let prev;
  return (id, text = "") => {
    prev && prev.cancel();

    return (prev = new CPromise((resolve, reject, { onCancel }) => {
      const $modal = $(id);

      text && $modal.find(".modal-body").text(text);

      $modal.modal("show");

      const dispose = () => {
        $modal.modal("hide");
        $modal.off("click", "button");
      };

      $modal.on("click", "button", function (e) {
        dispose();
        resolve($(this).data("action"));
      });

      $modal.on("hidden.bs.modal", () => {
        setTimeout(() => resolve("close"));
      });

      onCancel(dispose);
    })).finally(() => (prev = null));
  };
})();

$("#btn-run").on("click", async () => {
  showModal("#id_my_message_modal", "First message").then(
    (c) => console.log(`First modal result: ${c}`),
    (e) => console.warn(`First modal fail: ${e}`)
  );
  showModal("#id_my_message_modal", "Second message").then(
    (c) => console.log(`Second modal result: ${c}`),
    (e) => console.warn(`Second modal fail: ${e}`)
  );

  const promise = showModal("#id_my_message_modal", "Third message")
    .then(
      (c) => console.log(`Third modal result: ${c}`),
      (e) => console.warn(`Third modal fail: ${e}`)
    )
    .timeout(5000)
    .then(() => {
      return showModal(
        "#id_my_message_modal2",
        "Blue Pill or Red Pill?"
      ).then((v) => console.log(`Pill: ${v}`));
    });

  /*setTimeout(()=>{
    promise.cancel(); you can cancel the modal from your code 
  }, 1000);*/
});

Upvotes: 1

Related Questions