skip to Main Content

Is there a way to force an async method to stop running? Or is there a simple way to add support to stop running an async method?

I have this code that a user can start by clicking a start button:

async runTests() {
      await this.test1();
      await this.test2();
      await this.test3();
      await this.test4();
      await this.test5();
      await this.test6();
      await this.test7();
      await this.test8();
      await this.test9();
      await this.test10();
}

But now I need a way to stop it.

Is there a way to cancel this call or to exit out of it midway?

I would love something like,

window.cancelAsyncFunction(this.runTest);

Note: Typescript converts async calls into I believe some pseudo async JavaScript so I believe it’s possible to manually cancel it somehow but there would be benefits to having a native call if one exists.


Here is the code generated by Typescript:

var __extends = (this && this.__extends) || (function () {
    var extendStatics = function (d, b) {
        extendStatics = Object.setPrototypeOf ||
            ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
            function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };
        return extendStatics(d, b);
    };
    return function (d, b) {
        if (typeof b !== "function" && b !== null)
            throw new TypeError("Class extends value " + String(b) + " is not a constructor or null");
        extendStatics(d, b);
        function __() { this.constructor = d; }
        d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
    };
})();
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
var __generator = (this && this.__generator) || function (thisArg, body) {
    var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
    return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
    function verb(n) { return function (v) { return step([n, v]); }; }
    function step(op) {
        if (f) throw new TypeError("Generator is already executing.");
        while (g && (g = 0, op[0] && (_ = 0)), _) try {
            if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
            if (y = 0, t) op = [op[0] & 2, t.value];
            switch (op[0]) {
                case 0: case 1: t = op; break;
                case 4: _.label++; return { value: op[1], done: false };
                case 5: _.label++; y = op[1]; op = [0]; continue;
                case 7: op = _.ops.pop(); _.trys.pop(); continue;
                default:
                    if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
                    if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
                    if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
                    if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
                    if (t[2]) _.ops.pop();
                    _.trys.pop(); continue;
            }
            op = body.call(thisArg, _);
        } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
        if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
    }
};

And the method that has been transpiled:

    MyClass.prototype.runTests = function () {
        return __awaiter(this, void 0, void 0, function () {
            return __generator(this, function (_a) {
                switch (_a.label) {
                    case 0: return [4 /*yield*/, this.test1()];
                    case 1:
                        _a.sent();
                        return [4 /*yield*/, this.test2()];
                    case 2:
                        _a.sent();
                        return [4 /*yield*/, this.test3()];
                    // ... trimmed out more cases
                    case 3:
                        _a.sent();
                        this.updateStatus("Tests complete!");
                        return [2 /*return*/];
                }
            });
        });
    };

Sleep method that is mentioned in the comments:

   sleep(ms) {
      return new Promise(resolve => setTimeout(resolve, ms))
   }

Calling sleep:

await this.sleep(10);

2

Answers


  1. You can iterate your tests and stop with a class variable:

    class Test {
      constructor(){
        for(let i=1;i<=10;i++){
          this[`test${i}`] = () => new Promise(r=>setTimeout(r, 250)); 
        }
      }
      async runTests() {
         this.running = true;
         const tests = Array.from({length:10}, (_, i) => this[`test${i+1}`]);
         let i = 0;
         for(const test of tests){
            console.log(`running test ${++i}`);
            await test();
            if(!this.running){
              return;
            }
         }   
      }
      stop(){
        this.running = false;
      }
    }
    
    const test = new Test;
    test.runTests();
    <button onclick="test.stop()">STOP</button>

    A more sophisticated approach would be to use AbortController which you can cancel fetch with for example:

    class Test {
      constructor(){
        for(let i=1;i<=10;i++){
          this[`test${i}`] = async (id, signal) => {
            try{
              const r = await fetch('https://jsonplaceholder.typicode.com/todos/' + id, {signal});
              return r.json();
            }catch(e){
              if(e instanceof DOMException && e.name==='AbortError'){
                console.log(`fetch aborted`);
                return null;
              }
              throw e;
            }
          };
        }
      }
      async runTests({signal}) {
         let running = true;
         signal.addEventListener('abort', () => running = false);
         const tests = Array.from({length:10}, (_, i) => this[`test${i+1}`]);
         let i = 0;
         for(const test of tests){
            console.log(`running test ${++i}`);
            console.log(`result:`, JSON.stringify(await test(i, signal)));
            if(!running){
              return;
            }
         }   
      }
    }
    
    const test = new Test;
    const ctrl =  new AbortController;
    test.runTests(ctrl);
    <button onclick="ctrl.abort()">STOP</button>
    Login or Signup to reply.
  2. If your async code consists of fetch calls, you can use an AbortController to cancel the request.

    Otherwise when you create your promise, you’d need to expose a way to resolve or reject it externally.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search