Annuler une chaîne de promesse ECMAScript 6 vanille

110

Existe-t-il une méthode pour effacer les .thens d'une Promiseinstance JavaScript ?

J'ai écrit un cadre de test JavaScript au-dessus de QUnit . Le framework exécute les tests de manière synchrone en exécutant chacun dans un fichier Promise. (Désolé pour la longueur de ce bloc de code. Je l'ai commenté du mieux que je peux, donc cela semble moins fastidieux.)

/* Promise extension -- used for easily making an async step with a
       timeout without the Promise knowing anything about the function 
       it's waiting on */
$$.extend(Promise, {
    asyncTimeout: function (timeToLive, errorMessage) {
        var error = new Error(errorMessage || "Operation timed out.");
        var res, // resolve()
            rej, // reject()
            t,   // timeout instance
            rst, // reset timeout function
            p,   // the promise instance
            at;  // the returned asyncTimeout instance

        function createTimeout(reject, tempTtl) {
            return setTimeout(function () {
                // triggers a timeout event on the asyncTimeout object so that,
                // if we want, we can do stuff outside of a .catch() block
                // (may not be needed?)
                $$(at).trigger("timeout");

                reject(error);
            }, tempTtl || timeToLive);
        }

        p = new Promise(function (resolve, reject) {
            if (timeToLive != -1) {
                t = createTimeout(reject);

                // reset function -- allows a one-time timeout different
                //    from the one original specified
                rst = function (tempTtl) {
                    clearTimeout(t);
                    t = createTimeout(reject, tempTtl);
                }
            } else {
                // timeToLive = -1 -- allow this promise to run indefinitely
                // used while debugging
                t = 0;
                rst = function () { return; };
            }

            res = function () {
                clearTimeout(t);
                resolve();
            };

            rej = reject;
        });

        return at = {
            promise: p,
            resolve: res,
            reject: rej,
            reset: rst,
            timeout: t
        };
    }
});

/* framework module members... */

test: function (name, fn, options) {
    var mod = this; // local reference to framework module since promises
                    // run code under the window object

    var defaultOptions = {
        // default max running time is 5 seconds
        timeout: 5000
    }

    options = $$.extend({}, defaultOptions, options);

    // remove timeout when debugging is enabled
    options.timeout = mod.debugging ? -1 : options.timeout;

    // call to QUnit.test()
    test(name, function (assert) {
        // tell QUnit this is an async test so it doesn't run other tests
        // until done() is called
        var done = assert.async();
        return new Promise(function (resolve, reject) {
            console.log("Beginning: " + name);

            var at = Promise.asyncTimeout(options.timeout, "Test timed out.");
            $$(at).one("timeout", function () {
                // assert.fail() is just an extension I made that literally calls
                // assert.ok(false, msg);
                assert.fail("Test timed out");
            });

            // run test function
            var result = fn.call(mod, assert, at.reset);

            // if the test returns a Promise, resolve it before resolving the test promise
            if (result && result.constructor === Promise) {
                // catch unhandled errors thrown by the test so future tests will run
                result.catch(function (error) {
                    var msg = "Unhandled error occurred."
                    if (error) {
                        msg = error.message + "\n" + error.stack;
                    }

                    assert.fail(msg);
                }).then(function () {
                    // resolve the timeout Promise
                    at.resolve();
                    resolve();
                });
            } else {
                // if test does not return a Promise, simply clear the timeout
                // and resolve our test Promise
                at.resolve();
                resolve();
            }
        }).then(function () {
            // tell QUnit that the test is over so that it can clean up and start the next test
            done();
            console.log("Ending: " + name);
        });
    });
}

Si un test expire, ma promesse de délai d'expiration sera assert.fail()sur le test afin que le test soit marqué comme échoué, ce qui est bien beau, mais le test continue de s'exécuter car le test Promise ( result) attend toujours de le résoudre.

J'ai besoin d'un bon moyen d'annuler mon test. Je peux le faire en créant un champ sur le module de framework this.cancelTestou quelque chose du genre, et en vérifiant de temps en temps (par exemple au début de chaque then()itération) dans le test s'il faut annuler. Cependant, idéalement, je pourrais utiliser $$(at).on("timeout", /* something here */)pour effacer les then()s restants sur ma resultvariable, de sorte qu'aucun des autres tests ne soit exécuté.

Est-ce que quelque chose comme ça existe?

Mise à jour rapide

J'ai essayé d'utiliser Promise.race([result, at.promise]). Ça n'a pas marché.

Mise à jour 2 + confusion

Pour me débloquer, j'ai ajouté quelques lignes avec le mod.cancelTest/ polling dans l'idée de test. (J'ai également supprimé le déclencheur d'événement.)

return new Promise(function (resolve, reject) {
    console.log("Beginning: " + name);

    var at = Promise.asyncTimeout(options.timeout, "Test timed out.");
    at.promise.catch(function () {
        // end the test if it times out
        mod.cancelTest = true;
        assert.fail("Test timed out");
        resolve();
    });

    // ...
    
}).then(function () {
    // tell QUnit that the test is over so that it can clean up and start the next test
    done();
    console.log("Ending: " + name);
});

J'ai défini un point d'arrêt dans l' catchinstruction, et il est atteint. Ce qui me trouble maintenant, c'est que la then()déclaration n'est pas appelée. Des idées?

Mise à jour 3

J'ai compris la dernière chose. fn.call()lançait une erreur que je n'ai pas détectée, donc la promesse de test était rejetée avant de at.promise.catch()pouvoir la résoudre.

dx_over_dt
la source
Il est possible de faire une annulation avec des promesses ES6 mais ce n'est pas une propriété de la promesse (c'est plutôt une propriété de la fonction qui la renvoie). Je peux faire un petit exemple si cela vous intéresse.
Benjamin Gruenbaum
@BenjaminGruenbaum Je sais que ça fait presque un an, mais je suis toujours intéressé si vous avez le temps d'écrire un exemple. :)
dx_over_dt
1
Cela fait un an, mais cela a été officiellement discuté deux jours avant hier avec les jetons d'annulation et les promesses annulables passant à l'étape 1.
Benjamin Gruenbaum
3
La réponse ES6 à l'annulation d'une promesse est observable. Vous pouvez en savoir plus à ce sujet ici: github.com/Reactive-Extensions/RxJS
Frank Goortani
Lier ma réponse sur l'utilisation de la Prexbibliothèque pour l'annulation de promesse.
noseratio

Réponses:

75

Existe-t-il une méthode pour effacer les .thens d'une instance JavaScript Promise?

Non, au moins pas dans ECMAScript 6. Les promesses (et leurs thengestionnaires) ne sont pas annulables par défaut (malheureusement) . Il y a un peu de discussion sur es-discuter (par exemple ici ) sur la façon de faire cela de la bonne manière, mais quelle que soit l'approche qui gagnera, elle n'atterrira pas dans ES6.

Le point de vue actuel est que le sous-classement permettra de créer des promesses annulables en utilisant votre propre implémentation ( je ne sais pas si cela fonctionnera bien) .

Tant que le comité de langue n'a pas trouvé le meilleur moyen (ES7 espérons-le?), Vous pouvez toujours utiliser les implémentations Userland Promise, dont beaucoup sont annulées.

La discussion actuelle se trouve dans les versions https://github.com/domenic/cancelable-promise et https://github.com/bergus/promise-cancellation .

Bergi
la source
2
"Un peu de discussion" - Je peux créer un lien vers peut-être 30 discussions sur esdiscuss ou GitHub :) (sans parler de votre propre aide avec l'annulation dans bluebird 3.0)
Benjamin Gruenbaum
@BenjaminGruenbaum: Avez-vous ces liens prêts à être partagés quelque part? J'ai longtemps voulu résumer les opinions et les tentatives et poster une proposition pour esdiscuter, donc je serais heureux si je peux vérifier que je n'ai rien oublié.
Bergi
Je les ai à portée de main au travail - je les aurai donc dans 3-4 jours. Vous pouvez consulter les spécifications d'annulation de promesse sous promises-aplus pour un bon début.
Benjamin Gruenbaum
1
@ LUH3417: les fonctions "normales" sont simplement ennuyeuses à cet égard. Vous démarrez un programme et attendez qu'il soit terminé - ou vous killle faites et ignorez dans quel état peut-être étrange les effets secondaires ont laissé votre environnement (donc vous le jetez généralement aussi, par exemple toutes les sorties à moitié terminées). Cependant, les fonctions non bloquantes ou asynchrones sont conçues pour fonctionner dans des applications interactives, où vous souhaitez avoir un tel contrôle plus fin sur l'exécution des opérations en cours.
Bergi
6
Domenic a retiré la proposition TC39 ... ... cc @BenjaminGruenbaum
Sergio
50

Bien qu'il n'y ait pas de manière standard de faire cela dans ES6, il existe une bibliothèque appelée Bluebird pour gérer cela.

Il existe également une méthode recommandée décrite dans la documentation de réaction. Il ressemble à ce que vous avez dans vos 2 et 3 mises à jour.

const makeCancelable = (promise) => {
  let hasCanceled_ = false;

  const wrappedPromise = new Promise((resolve, reject) => {
    promise.then((val) =>
      hasCanceled_ ? reject({isCanceled: true}) : resolve(val)
    );
    promise.catch((error) =>
      hasCanceled_ ? reject({isCanceled: true}) : reject(error)
    );
  });

  return {
    promise: wrappedPromise,
    cancel() {
      hasCanceled_ = true;
    },
  };
};

const cancelablePromise = makeCancelable(
  new Promise(r => component.setState({...}}))
);

cancelablePromise
  .promise
  .then(() => console.log('resolved'))
  .catch((reason) => console.log('isCanceled', reason.isCanceled));

cancelablePromise.cancel(); // Cancel the promise

Tiré de: https://facebook.github.io/react/blog/2015/12/16/ismounted-antipattern.html

Michael Yagudaev
la source
1
cette définition de l'annulé ne fait que rejeter la promesse. cela dépend de la définition de «annulé».
Alexander Mills
1
Et que se passe-t-il si vous souhaitez annuler un ensemble de promesses?
Matthieu Brucher
1
Le problème avec cette approche est que si vous avez une promesse qui ne sera jamais résolue ou rejetée, elle ne sera jamais annulée.
DaNeSh
2
C'est partiellement vrai, mais si vous avez une longue chaîne de promesses, cette approche ne fonctionnera pas.
Veikko Karsikko
11

Je suis vraiment surpris que personne ne mentionne Promise.racecomme candidat pour ceci:

const actualPromise = new Promise((resolve, reject) => { setTimeout(resolve, 10000) });
let cancel;
const cancelPromise = new Promise((resolve, reject) => {
    cancel = reject.bind(null, { canceled: true })
})

const cancelablePromise = Object.assign(Promise.race([actualPromise, cancelPromise]), { cancel });
Pho3nixHun
la source
3
Je ne crois pas que cela fonctionne. Si vous modifiez la promesse de journalisation, l'exécution cancel()entraînera toujours l'appel du journal. `` `` const actualPromise = new Promise ((résoudre, rejeter) => {setTimeout (() => {console.log ('réel appelé'); résoudre ()}, 10000)}); `` ``
shmck
2
La question était de savoir comment annuler une promesse (=> arrêter les enchaînés thenà exécuter), pas comment annuler setTimeout(=> clearTimeout) ou le code synchrone, où à moins que vous ne mettiez un if après chaque ligne ( if (canceled) return), cela ne peut pas être réalisé. (Ne faites pas ça)
Pho3nixHun
10
const makeCancelable = promise => {
    let rejectFn;

    const wrappedPromise = new Promise((resolve, reject) => {
        rejectFn = reject;

        Promise.resolve(promise)
            .then(resolve)
            .catch(reject);
    });

    wrappedPromise.cancel = () => {
        rejectFn({ canceled: true });
    };

    return wrappedPromise;
};

Usage:

const cancelablePromise = makeCancelable(myPromise);
// ...
cancelablePromise.cancel();
Slava M
la source
5

Il est en fait impossible d'arrêter l'exécution de la promesse, mais vous pouvez détourner le rejet et l'appeler à partir de la promesse elle-même.

class CancelablePromise {
  constructor(executor) {
    let _reject = null;
    const cancelablePromise = new Promise((resolve, reject) => {
      _reject = reject;
      return executor(resolve, reject);
    });
    cancelablePromise.cancel = _reject;

    return cancelablePromise;
  }
}

Usage:

const p = new CancelablePromise((resolve, reject) => {
  setTimeout(() => {
    console.log('resolved!');
    resolve();
  }, 2000);
})

p.catch(console.log);

setTimeout(() => {
  p.cancel(new Error('Messed up!'));
}, 1000);
Nikksan
la source
1
@dx_over_dt Votre modification serait un excellent commentaire, mais pas une modification. Veuillez laisser ces modifications de fond à la compétence du PO (à moins que le message ne soit marqué comme Community Wiki, bien sûr).
TylerH
@TylerH est donc le but de l'édition pour corriger les fautes de frappe et autres? Ou pour mettre à jour les informations lorsqu'elles deviennent obsolètes? Je suis nouveau dans la possibilité de modifier le privilège des publications d'autres personnes
dx_over_dt
@dx_over_dt Oui, l'édition consiste à améliorer les messages en corrigeant les fautes de frappe, les erreurs grammaticales et en ajoutant une coloration syntaxique (si quelqu'un publie juste un tas de code mais ne le met pas en retrait ou ne le marque pas avec `` '' par exemple). L'ajout de contenu de fond comme des explications supplémentaires ou des raisonnements / justifications des choses est généralement du ressort de la personne qui a publié la réponse. Vous êtes libre de le suggérer dans les commentaires, et OP sera informé du commentaire et pourra ensuite y répondre, ou il pourra simplement incorporer votre suggestion dans le message lui-même.
TylerH
@dx_over_dt Les exceptions sont si un message est marqué "Wiki de la communauté" indiquant qu'il est destiné à servir de message collaboratif (par exemple, comme Wikipedia), ou s'il y a des problèmes graves avec le message, comme un langage grossier / abusif, un contenu dangereux / préjudiciable ( par exemple des suggestions ou des codes susceptibles de vous transmettre un virus ou de vous faire arrêter, etc.), ou des informations personnelles telles que des dossiers médicaux, des numéros de téléphone, des cartes de crédit, etc. n'hésitez pas à les supprimer vous-même.
TylerH
Il convient de noter que la raison pour laquelle l'exécution ne peut pas être interrompue dans une promesse est que JavaScript est monothread. Pendant que la fonction de promesse est en cours d'exécution, rien d'autre n'est en cours d'exécution, il n'y a donc rien pour déclencher l'arrêt de l'exécution.
dx_over_dt
2

Voici notre implémentation https://github.com/permettez-moi-de-construire/cancellable-promise

Utilisé comme

const {
  cancellablePromise,
  CancelToken,
  CancelError
} = require('@permettezmoideconstruire/cancellable-promise')

const cancelToken = new CancelToken()

const initialPromise = SOMETHING_ASYNC()
const wrappedPromise = cancellablePromise(initialPromise, cancelToken)


// Somewhere, cancel the promise...
cancelToken.cancel()


//Then catch it
wrappedPromise
.then((res) => {
  //Actual, usual fulfill
})
.catch((err) => {
  if(err instanceOf CancelError) {
    //Handle cancel error
  }

  //Handle actual, usual error
})

lequel :

  • Ne touche pas à l'API Promise
  • Laissez-nous faire une annulation supplémentaire à l'intérieur de l' catchappel
  • Comptez sur le rejet de l' annulation plutôt que sur la résolution, contrairement à toute autre proposition ou mise en œuvre

Tirages et commentaires bienvenus

Cyril CHAPON
la source
2

La promesse peut être annulée à l'aide de AbortController.

Existe-t-il une méthode pour effacer alors: oui, vous pouvez rejeter la promesse avec un AbortControllerobjet, puis le promisecontournera tout puis les blocs et ira directement au bloc catch.

Exemple:

import "abortcontroller-polyfill";

let controller = new window.AbortController();
let signal = controller.signal;
let elem = document.querySelector("#status")

let example = (signal) => {
    return new Promise((resolve, reject) => {
        let timeout = setTimeout(() => {
            elem.textContent = "Promise resolved";
            resolve("resolved")
        }, 2000);

        signal.addEventListener('abort', () => {
            elem.textContent = "Promise rejected";
            clearInterval(timeout);
            reject("Promise aborted")
        });
    });
}

function cancelPromise() {
    controller.abort()
    console.log(controller);
}

example(signal)
    .then(data => {
        console.log(data);
    })
    .catch(error => {
        console.log("Catch: ", error)
    });

document.getElementById('abort-btn').addEventListener('click', cancelPromise);

HTML


    <button type="button" id="abort-btn" onclick="abort()">Abort</button>
    <div id="status"> </div>

Remarque: besoin d'ajouter polyfill, non pris en charge dans tous les navigateurs.

Exemple en direct

Modifier elegant-lake-5jnh3

Sohail
la source
1

version simple :

donnez simplement la fonction de rejet.

function Sleep(ms,cancel_holder) {

 return new Promise(function(resolve,reject){
  var done=false; 
  var t=setTimeout(function(){if(done)return;done=true;resolve();}, ms);
  cancel_holder.cancel=function(){if(done)return;done=true;if(t)clearTimeout(t);reject();} 
 })
}

une solution d'emballage (usine)

la solution que j'ai trouvée est de passer un objet cancel_holder. il aura une fonction d'annulation. s'il a une fonction d'annulation, il est annulable.

Cette fonction d'annulation rejette la promesse avec une erreur («annulée»).

Avant de résoudre, rejeter ou on_cancel, empêchez la fonction d'annulation d'être appelée sans raison.

J'ai trouvé pratique de passer l'action d'annulation par injection

function cancelablePromise(cancel_holder,promise_fn,optional_external_cancel) {
  if(!cancel_holder)cancel_holder={};
  return new Promise( function(resolve,reject) {
    var canceled=false;
    var resolve2=function(){ if(canceled) return; canceled=true; delete cancel_holder.cancel; resolve.apply(this,arguments);}
    var reject2=function(){ if(canceled) return; canceled=true; delete cancel_holder.cancel; reject.apply(this,arguments);}
    var on_cancel={}
    cancel_holder.cancel=function(){
      if(canceled) return; canceled=true;

      delete cancel_holder.cancel;
      cancel_holder.canceled=true;

      if(on_cancel.cancel)on_cancel.cancel();
      if(optional_external_cancel)optional_external_cancel();

      reject(new Error('canceled'));
    };

    return promise_fn.call(this,resolve2,reject2,on_cancel);        
  });
}

function Sleep(ms,cancel_holder) {

 return cancelablePromise(cancel_holder,function(resolve,reject,oncacnel){

  var t=setTimeout(resolve, ms);
  oncacnel.cancel=function(){if(t)clearTimeout(t);}     

 })
}


let cancel_holder={};

// meanwhile in another place it can be canceled
setTimeout(function(){  if(cancel_holder.cancel)cancel_holder.cancel(); },500) 

Sleep(1000,cancel_holder).then(function() {
 console.log('sleept well');
}, function(e) {
 if(e.message!=='canceled') throw e;
 console.log('sleep interrupted')
})
Shimon Doodkin
la source
1

Essayez promise-abortable : https://www.npmjs.com/package/promise-abortable

$ npm install promise-abortable
import AbortablePromise from "promise-abortable";

const timeout = new AbortablePromise((resolve, reject, signal) => {
  setTimeout(reject, timeToLive, error);
  signal.onabort = resolve;
});

Promise.resolve(fn()).then(() => {
  timeout.abort();
});
Devi
la source
1

Si votre code est placé dans une classe, vous pouvez utiliser un décorateur pour cela. Vous avez un tel décorateur dans les utils-décorateurs ( npm install --save utils-decorators). Cela annulera l'appel précédent de la méthode décorée si avant la résolution de l'appel précédent, un autre appel a été effectué pour cette méthode spécifique.

import {cancelPrevious} from 'utils-decorators';

class SomeService {

   @cancelPrevious()
   doSomeAsync(): Promise<any> {
    ....
   }
}

https://github.com/vlio20/utils-decorators#cancelprevious-method

vlio20
la source
0

Si vous voulez arrêter l'exécution de tous les thens / catchs, vous pouvez le faire en injectant une promesse qui ne se résoudra jamais. Il a probablement des reprocalisations de fuite de mémoire, mais cela résoudra le problème et ne devrait pas causer trop de perte de mémoire dans la plupart des applications.

new Promise((resolve, reject) => {
    console.log('first chain link executed')
    resolve('daniel');
}).then(name => {
    console.log('second chain link executed')
    if (name === 'daniel') {
        // I don't want to continue the chain, return a new promise
        // that never calls its resolve function
        return new Promise((resolve, reject) => {
            console.log('unresolved promise executed')
        });
    }
}).then(() => console.log('last chain link executed'))

// VM492:2 first chain link executed
// VM492:5 second chain link executed
// VM492:8 unresolved promise executed
DanLatimer
la source
0

Définissez une propriété «annulée» sur la promesse pour signaler then()et catch()quitter tôt. C'est très efficace, en particulier dans les Web Workers qui ont des microtâches existantes en file d'attente dans les promesses des onmessagegestionnaires.

// Queue task to resolve Promise after the end of this script
const promise = new Promise(resolve => setTimeout(resolve))

promise.then(_ => {
  if (promise.canceled) {
    log('Promise cancelled.  Exiting early...');
    return;
  }

  log('No cancelation signaled.  Continue...');
})

promise.canceled = true;

function log(msg) {
  document.body.innerHTML = msg;
}

AnthumChris
la source
0

La réponse de @Michael Yagudaev fonctionne pour moi.

Mais la réponse originale n'a pas enchaîné la promesse emballée avec .catch () pour gérer la gestion des rejets, voici mon amélioration en plus de la réponse de @Michael Yagudaev:

const makeCancelablePromise = promise => {
  let hasCanceled = false;
  const wrappedPromise = new Promise((resolve, reject) => {
    promise
      .then(val => (hasCanceled ? reject({ isCanceled: true }) : resolve(val)))
      .catch(
        error => (hasCanceled ? reject({ isCanceled: true }) : reject(error))
      );
  });

  return {
    promise: wrappedPromise,
    cancel() {
      hasCanceled = true;
    }
  };
};

// Example Usage:
const cancelablePromise = makeCancelable(
  new Promise((rs, rj) => {
    /*do something*/
  })
);
cancelablePromise.promise.then(() => console.log('resolved')).catch(err => {
  if (err.isCanceled) {
    console.log('Wrapped promise canceled');
    return;
  }
  console.log('Promise was not canceled but rejected due to errors: ', err);
});
cancelablePromise.cancel();

la source
0

Si p est une variable qui contient une promesse, alors p.then(empty);devrait rejeter la promesse lorsqu'elle se termine finalement ou si elle est déjà complète (oui, je sais que ce n'est pas la question d'origine, mais c'est ma question). "vide" est function empty() {}. Je ne suis qu'un débutant et j'ai probablement tort, mais ces autres réponses semblent trop compliquées. Les promesses sont censées être simples.

David Spector
la source
0

Je travaille toujours sur cette idée, mais voici comment j'ai implémenté une promesse annulable en utilisant setTimeoutcomme exemple.

L'idée est qu'une promesse est résolue ou rejetée chaque fois que vous l'avez décidé, il devrait donc s'agir de décider quand vous voulez annuler, de satisfaire le critère, puis d'appeler la reject()fonction vous-même.

  • Premièrement, je pense qu'il y a deux raisons de terminer une promesse tôt: pour en finir avec (ce que j'ai appelé résoudre ) et pour annuler (que j'ai appelé rejet ). Bien sûr, c'est juste mon sentiment. Bien sûr, il existe une Promise.resolve()méthode, mais elle est dans le constructeur lui-même et renvoie une promesse résolue factice. Cette resolve()méthode d' instance résout en fait un objet de promesse instancié.

  • En second lieu , vous pouvez heureusement ajouter quoi que ce soit vous aimez à un objet de promesse nouvellement créé avant de le retourner, et je viens d' ajouter resolve()et de reject()méthodes pour le rendre autonome.

  • Troisièmement, l'astuce est de pouvoir accéder à l'exécuteur resolveet aux rejectfonctions plus tard, donc je les ai simplement stockés dans un objet simple à partir de la fermeture.

Je pense que la solution est simple et je ne vois aucun problème majeur avec elle.

function wait(delay) {
  var promise;
  var timeOut;
  var executor={};
  promise=new Promise(function(resolve,reject) {
    console.log(`Started`);
    executor={resolve,reject};  //  Store the resolve and reject methods
    timeOut=setTimeout(function(){
      console.log(`Timed Out`);
      resolve();
    },delay);
  });
  //  Implement your own resolve methods,
  //  then access the stored methods
      promise.reject=function() {
        console.log(`Cancelled`);
        clearTimeout(timeOut);
        executor.reject();
      };
      promise.resolve=function() {
        console.log(`Finished`);
        clearTimeout(timeOut);
        executor.resolve();
      };
  return promise;
}

var promise;
document.querySelector('button#start').onclick=()=>{
  promise=wait(5000);
  promise
  .then(()=>console.log('I have finished'))
  .catch(()=>console.log('or not'));
};
document.querySelector('button#cancel').onclick=()=>{ promise.reject(); }
document.querySelector('button#finish').onclick=()=>{ promise.resolve(); }
<button id="start">Start</button>
<button id="cancel">Cancel</button>
<button id="finish">Finish</button>

Manngo
la source