wake-up-neo.com

Kündigen Sie eine Vanilla ECMAScript 6 Promise-Kette

Gibt es eine Methode zum Löschen der .thens einer JavaScript Promise-Instanz?

Ich habe ein JavaScript-Test-Framework auf QUnit geschrieben. Das Framework führt Tests synchron aus, indem jeder in einer Promise ausgeführt wird. (Sorry für die Länge dieses Codeblocks. Ich habe es so gut ich kann kommentiert, so dass es weniger langweilig ist.)

/* 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);
        });
    });
}

Wenn ein Test abgelaufen ist, wird mein Timeout Promise assert.fail() im Test ausgeführt, so dass der Test als nicht bestanden markiert wird. Dies ist gut und gut. Der Test wird jedoch fortgesetzt, da der Test Promise (result) noch auf die Auflösung wartet.

Ich brauche einen guten Weg, um meinen Test abzubrechen. Ich kann dies tun, indem ich ein Feld im Framework-Modul this.cancelTest oder etwas erstellt habe und regelmäßig (z. B. zu Beginn jeder then()-Iteration) während des Tests prüfe, ob es abgebrochen werden soll. Idealerweise könnte ich jedoch $$(at).on("timeout", /* something here */) verwenden, um die verbleibenden then()s auf meiner Variable result zu löschen, sodass der restliche Test nicht ausgeführt wird.

Gibt es so etwas?

Schnelles Update

Ich habe es mit Promise.race([result, at.promise]) versucht. Es hat nicht funktioniert.

Update 2 + Verwirrung

Um mich zu entsperren, habe ich ein paar Zeilen mit dem mod.cancelTest/polling in die Testidee eingefügt. (Ich habe auch den Ereignisauslöser entfernt.)

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);
});

Ich habe einen Haltepunkt in der catch-Anweisung festgelegt, und es wird getroffen. Was mich jetzt verwirrt, ist, dass die then()-Anweisung nicht aufgerufen wird. Ideen?

Update 3

Das letzte herausgefunden. fn.call() hat einen Fehler ausgelöst, den ich nicht erkannt habe. Das Testversprechen wurde abgelehnt, bevor at.promise.catch() es beheben konnte.

75
dx_over_dt

Gibt es eine Methode zum Löschen der .thens einer JavaScript Promise-Instanz?

Nein, zumindest nicht in ECMAScript 6. Versprechen (und ihre then-Handler) sind standardmäßig nicht aufgehoben (leider). Es-Diskussion (z. B. hier ) wird diskutiert, wie dies auf die richtige Art und Weise getan werden kann, aber bei jedem Ansatz wird es nicht in ES6 landen.

Der gegenwärtige Standpunkt ist, dass Subclassing es ermöglicht, stornierbare Versprechen mit Ihrer eigenen Implementierung zu erstellen (nicht sicher, wie gut das funktioniert).

Bis das Sprachkomitee den besten Weg herausgefunden hat (hoffentlich ES7?), Können Sie noch Userland Promise-Implementierungen verwenden, von denen viele Funktionen aufheben.

Aktuelle Diskussionen finden sich in den Entwürfen https://github.com/domenic/cancelable-promise und https://github.com/bergus/promise-cancellation .

50
Bergi

In ES6 gibt es keine Standardmethode, aber es gibt eine Bibliothek namens Bluebird , um dies zu handhaben.

Es gibt auch einen empfohlenen Weg, der als Teil der Reaktordokumentation beschrieben wird. Es sieht ähnlich aus, was Sie in Ihren 2. und 3. Updates haben.

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

Entnommen aus: https://facebook.github.io/react/blog/2015/12/16/ismounted-antipattern.html

42
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;
};

Verwendungszweck:

const cancelablePromise = makeCancelable(myPromise);
// ...
cancelablePromise.cancel();
4
Slava M

Ich bin wirklich überrascht, dass niemand Promise.race als Kandidaten für dieses Thema erwähnt:

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 });
3
Pho3nixHun

Es gibt ein paar npm-Bibliotheken für kündbare Versprechen.

  1. p-cancelablehttps://github.com/sindresorhus/p-cancelable

  2. cancelable-promisehttps://github.com/alkemics/CancelablePromise

1
WebBrother

Es ist tatsächlich unmöglich, die Ausführung des Versprechens zu stoppen, aber Sie können die Ablehnung entführen und sie aus dem Versprechen selbst aufrufen.

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

    return cancelablePromise;
  }
}

Verwendungszweck:

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

p.catch(console.log);

setTimeout(() => {
  p.cancel(new Error('Fucked up!'));
}, 1000);
1
nikksan

einfache Version :

geben Sie einfach die Ablehnungsfunktion aus.

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();} 
 })
}

eine Wrapperlösung (Fabrik)

die Lösung, die ich gefunden habe, besteht darin, ein cancel_holder-Objekt zu übergeben. es wird eine Abbruchfunktion haben. Wenn es eine Abbruchfunktion hat, kann es abgebrochen werden.

Diese Abbruchfunktion lehnt das Versprechen mit Fehler ab ('storniert').

Bevor auflösen, ablehnen oder on_cancel verhindern, dass die Abbruchfunktion ohne Grund aufgerufen wird.

Ich habe es als zweckmäßig erachtet, die Abbruchaktion durch Injektion zu übergeben

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')
})
1
Shimon Doodkin

Hier ist unsere Implementierung https://github.com/permettez-moi-de-construire/cancellable-promise

Verwendet wie

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
})

welche :

  • Berührt die Promise-API nicht
  • Lassen Sie uns eine weitere Kündigung innerhalb des Aufrufs catch vornehmen
  • Verlassen Sie sich darauf, dass die Annullierung abgelehnt statt gelöst ist, anders als bei anderen Vorschlägen oder Implementierungen

Zieht und kommentiert willkommen

0
Cyril CHAPON

Versuchen Sie 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();
});
0
Devi

Wenn Sie verhindern möchten, dass alle Zwischenfälle/Fänge ausgeführt werden, können Sie dies tun, indem Sie ein Versprechen einreichen, das niemals gelöst wird. Es hat wahrscheinlich Speicherlecks, aber das Problem wird dadurch behoben, und es sollte in den meisten Anwendungen nicht zu viel Speicherplatz verschwendet werden.

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
0
DanLatimer

@Michael Yagudaevs Antwort funktioniert für mich.

Aber die ursprüngliche Antwort verkettete das umwickelte Versprechen nicht mit .catch (), um die Zurückweisung zu behandeln. Hier ist meine Verbesserung zusätzlich zu @Michael Yagudaevs Antwort:

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();
0
user10175111

Legen Sie im Promise eine "stornierte" Eigenschaft fest, um zu signalisieren, dass then() und catch() vorzeitig beendet werden. Dies ist besonders effektiv bei Web Workern, bei denen bereits vorhandene Mikrotasks in Promises von onmessage-Handlern in die Warteschlange gestellt werden.

// 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;
}

0
AnthumChris

Wenn p eine Variable ist, die ein Versprechen enthält, sollte p.then(empty); das Versprechen verwerfen, wenn es eventuell abgeschlossen ist oder bereits abgeschlossen ist (ja, ich weiß, dass dies nicht die ursprüngliche Frage ist, aber es ist meine Frage). "leer" ist function empty() {}. Ich bin nur ein Anfänger und wahrscheinlich falsch, aber diese anderen Antworten erscheinen zu kompliziert. Versprechen sollen einfach sein.

0
David Spector