Anuluj waniliowy łańcuch obietnic ECMAScript 6


110

Czy istnieje metoda czyszczenia .thens Promiseinstancji JavaScript ?

Napisałem framework testowy JavaScript w oparciu o QUnit . Struktura uruchamia testy synchronicznie, uruchamiając każdy z nich w Promise. (Przepraszam za długość tego bloku kodu. Skomentowałem go najlepiej, jak potrafiłem, więc jest mniej uciążliwy).

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

Jeśli test przekroczy limit czasu, moja obietnica przekroczenia limitu czasu włączy assert.fail()się do testu, tak że test zostanie oznaczony jako nieudany, co jest w porządku, ale test jest kontynuowany, ponieważ test Promise ( result) nadal czeka, aby go rozwiązać.

Potrzebuję dobrego sposobu na anulowanie testu. Mogę to zrobić, tworząc pole w module frameworka this.cancelTestlub czymś w tym rodzaju i sprawdzając co jakiś czas (np. Na początku każdej then()iteracji) w ramach testu, czy się anulować. Jednak w idealnym przypadku mógłbym użyć $$(at).on("timeout", /* something here */)do wyczyszczenia pozostałych then()s na mojej resultzmiennej, aby żadna z pozostałych testów nie została uruchomiona.

Czy coś takiego istnieje?

Szybka aktualizacja

Próbowałem użyć Promise.race([result, at.promise]). To nie zadziałało.

Aktualizacja 2 + zamieszanie

Aby mnie odblokować, dodałem kilka wierszy z mod.cancelTest/ odpytywaniem w pomyśle testowym. (Usunąłem też wyzwalacz zdarzenia).

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

Ustawiłem punkt przerwania w catchinstrukcji i jest trafiony. Co mnie teraz dezorientuje, to fakt, że to then()stwierdzenie nie jest wywoływane. Pomysły?

Zaktualizuj 3

Zrozumiałem ostatnią rzecz. fn.call()wyrzucał błąd, którego nie złapałem, więc obietnica testowa została odrzucona, zanim at.promise.catch()mogła go rozwiązać.


Możliwe jest anulowanie za pomocą obietnic ES6, ale nie jest to właściwość obietnicy (raczej - jest to właściwość funkcji, która ją zwraca). Jeśli jesteś zainteresowany, mogę podać krótki przykład.
Benjamin Gruenbaum

@BenjaminGruenbaum Wiem, że minął już prawie rok, ale nadal jestem zainteresowany, jeśli masz czas, aby napisać przykład. :)
dx_over_dt

1
Minął rok temu, ale zostało oficjalnie omówione dwa dni wcześniej z tokenami anulowania i obietnicami do anulowania przechodzącymi do etapu 1.
Benjamin Gruenbaum

3
Odpowiedź ES6 na anulowanie obietnicy jest obserwowalna. Możesz przeczytać więcej na ten temat tutaj: github.com/Reactive-Extensions/RxJS
Frank Goortani

Łączę moją odpowiedź na temat korzystania z Prexbiblioteki do anulowania obietnicy.
noseratio

Odpowiedzi:


75

Czy istnieje metoda czyszczenia .theninstancji JavaScript Promise?

Nie. Przynajmniej nie w ECMAScript 6. Obietnice (i ich thenopiekunowie) domyślnie nie podlegają anulowaniu (niestety) . Trwa dyskusja na temat es-dyskusji (np. Tutaj ) o tym, jak to zrobić we właściwy sposób, ale jakiekolwiek podejście wygra, nie wyląduje w ES6.

Obecne stanowisko jest takie, że tworzenie podklas pozwoli na tworzenie anulowalnych obietnic przy użyciu własnej implementacji (nie wiem, jak dobrze to zadziała) .

Dopóki komitet językowy nie wymyśli najlepszego sposobu (miejmy nadzieję, że ES7?) Nadal można używać implementacji platformy użytkownika Promise, z których wiele jest anulowanych.

Bieżąca dyskusja znajduje się w wersjach roboczych https://github.com/domenic/cancelable-promise i https://github.com/bergus/promise-cancellation .


2
„Trochę dyskusji” - mogę linkować do może 30 wątków na esdiscuss lub GitHub :) (nie wspominając o twojej własnej pomocy przy anulowaniu w bluebird 3.0)
Benjamin Gruenbaum

@BenjaminGruenbaum: Czy masz gdzieś te linki gotowe do udostępnienia? Od dawna chciałem podsumować opinie i próby oraz zamieścić propozycję esdyskusji, więc byłbym szczęśliwy, gdybym mógł sprawdzić, czy o niczym nie zapomniałem.
Bergi

Mam je pod ręką w pracy - więc będę je mieć za 3-4 dni. Na dobry początek możesz sprawdzić specyfikację anulowania obietnicy w promises-aplus.
Benjamin Gruenbaum

1
@ LUH3417: „normalne” funkcje są po prostu nudne pod tym względem. Uruchamiasz program i czekasz, aż się zakończy - albo killgo ignorujesz, w jakim być może dziwnym stanie, w jakim efekty uboczne pozostawiły twoje środowisko (więc zazwyczaj po prostu to wyrzucasz, np. Niedokończone wyjścia). Funkcje nieblokujące lub asynchroniczne są jednak zbudowane do pracy w aplikacjach interaktywnych, w których chcesz mieć lepszą kontrolę nad wykonywaniem bieżących operacji.
Bergi

6
Domenic usunął propozycję TC39 ... ... cc @BenjaminGruenbaum
Sergio

50

Chociaż w ES6 nie ma standardowego sposobu na zrobienie tego, istnieje biblioteka o nazwie Bluebird, która to umożliwia.

Istnieje również zalecany sposób opisany jako część dokumentacji reagowania. Wygląda podobnie do tego, co masz w 2 i 3 aktualizacjach.

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

Zaczerpnięte z: https://facebook.github.io/react/blog/2015/12/16/ismounted-antipattern.html


1
ta definicja anulowania jest po prostu odrzuceniem obietnicy. zależy to od definicji „anulowane”.
Alexander Mills

1
A co się stanie, jeśli zechcesz anulować zestaw obietnic?
Matthieu Brucher

1
Problem z tym podejściem polega na tym, że jeśli masz Obietnicę, która nigdy nie zostanie rozwiązana ani odrzucona, nigdy nie zostanie anulowana.
DaNeSh

2
Jest to częściowo poprawne, ale jeśli masz długi łańcuch obietnic, to podejście nie zadziała.
Veikko Karsikko

11

Jestem naprawdę zaskoczony, że nikt nie wymienia Promise.racejako kandydata do tego:

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
Nie wierzę, że to działa. Jeśli zmienisz obietnicę na rejestrowanie, uruchomienie cancel()nadal spowoduje wywołanie dziennika. `` `` const rzeczywistePromise = new Promise ((rozstrzygnij, odrzuć) => {setTimeout (() => {console.log ('rzeczywiste wezwanie'); rozwiąż ()}, 10000)}); ``
shmck

2
Pytanie brzmiało, jak anulować obietnicę (=> zatrzymaj łańcuchy thendo wykonania), a nie jak anulować setTimeout(=> clearTimeout) lub kod synchroniczny, gdzie nie można if (canceled) returntego osiągnąć , chyba że umieścisz if po każdej linii ( ). (Nie rób tego)
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;
};

Stosowanie:

const cancelablePromise = makeCancelable(myPromise);
// ...
cancelablePromise.cancel();

5

W rzeczywistości niemożliwe jest zatrzymanie realizacji obietnicy, ale możesz przejąć odrzucenie i wywołać to z samej obietnicy.

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

    return cancelablePromise;
  }
}

Stosowanie:

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

p.catch(console.log);

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

1
@dx_over_dt Twoja zmiana byłaby świetnym komentarzem, ale nie zmianą. Prosimy o pozostawienie takich merytorycznych zmian w kompetencjach PO (oczywiście o ile post jest oznaczony jako Społeczność Wiki).
TylerH,

@TylerH, więc czy celem edycji jest poprawianie literówek i tym podobnych? Lub aktualizować informacje, gdy stają się nieaktualne? Jestem nowy w możliwości edytowania uprawnień do wpisów innych osób.
dx_over_dt

@dx_over_dt Tak, edycja ma na celu ulepszenie postów poprzez poprawienie literówek, błędów gramatycznych i dodanie podświetlania składni (jeśli ktoś po prostu publikuje pakiet kodu, ale nie wcina go ani nie otagowuje `` na przykład). Dodawanie merytorycznej treści, takiej jak dodatkowe wyjaśnienia lub uzasadnienie / uzasadnienie rzeczy, jest zazwyczaj w gestii osoby, która zamieściła odpowiedź. Możesz zasugerować to w komentarzach, a OP zostanie powiadomiony o komentarzu i będzie mógł na niego odpowiedzieć, lub może po prostu uwzględnić twoją sugestię w poście.
TylerH,

@dx_over_dt Wyjątkami są sytuacje, w których post jest oznaczony jako „Społeczność Wiki”, co oznacza, że ​​ma służyć jako wspólny post (np. Wikipedia) lub jeśli występują poważne problemy z postem, takie jak niegrzeczny / obraźliwy język, niebezpieczna / szkodliwa zawartość ( np. sugestie lub kod, który może spowodować wirus lub aresztowanie itp.) lub informacje osobiste, takie jak karty zdrowia, numery telefonów, karty kredytowe itp .; możesz je usunąć samodzielnie.
TylerH,

Warto zauważyć, że przyczyną nie można zatrzymać wykonania w ramach obietnicy jest to, że JavaScript jest jednowątkowy. Podczas wykonywania funkcji obietnicy nic innego nie działa, więc nie ma nic, co mogłoby spowodować zatrzymanie wykonywania.
dx_over_dt


2

Oto nasza realizacja https://github.com/permettez-moi-de-construire/cancellable-promise

Używane jak

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

który :

  • Nie dotyka Promise API
  • Zróbmy dalsze anulowanie catchpołączenia wewnętrznego
  • Polegaj na odrzuceniu, a nie rozwiązaniu anulowania, w przeciwieństwie do innych propozycji lub implementacji

Mile widziane ściągnięcia i komentarze


2

Obietnicę można anulować za pomocą AbortController.

Czy jest więc metoda na wyczyszczenie: tak, możesz odrzucić obietnicę z AbortControllerobiektem, a wtedy promisebędzie omijać wszystkie, a następnie bloki i przejść bezpośrednio do bloku catch.

Przykład:

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>

Uwaga: trzeba dodać wypełnienie, nie jest obsługiwane we wszystkich przeglądarkach.

Przykład na żywo

Edytuj elegancki-jezioro-5jnh3


1

prosta wersja :

po prostu daj funkcję odrzucania.

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

rozwiązanie do pakowania (fabryka)

rozwiązaniem, które znalazłem, jest przekazanie obiektu cancel_holder. będzie miał funkcję anulowania. jeśli ma funkcję anulowania, można ją anulować.

Ta funkcja anulowania odrzuca obietnicę z błędem („anulowano”).

Przed rozwiązaniem, odrzucaj lub on_cancel zapobiega wywoływaniu funkcji anulowania bez powodu.

Zauważyłem, że wygodnie jest przekazać akcję anulowania przez wstrzyknięcie

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

Wypróbuj możliwość anulowania obietnicy : 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();
});

1

Jeśli twój kod jest umieszczony w klasie, możesz użyć do tego dekoratora. Masz takiego dekoratora w narzędziu -dekoratorach ( npm install --save utils-decorators). Anuluje poprzednie wywołanie dekorowanej metody, jeśli przed rozstrzygnięciem poprzedniego wywołania zostało wykonane inne wywołanie tej konkretnej metody.

import {cancelPrevious} from 'utils-decorators';

class SomeService {

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

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


0

Jeśli chcesz powstrzymać wykonywanie wszystkich następnie / złapań, możesz to zrobić, wstrzykując obietnicę, która nigdy się nie rozwiąże. Prawdopodobnie ma zmiany orientacji wycieku pamięci, ale rozwiąże problem i nie powinno powodować zbytniego marnowania pamięci w większości aplikacji.

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

Ustaw właściwość „anulowana” w Obietnicy, aby zasygnalizować then()i catch()wyjść wcześniej. Jest bardzo skuteczny, szczególnie w przypadku pracowników sieci, którzy mają istniejące mikrozadania w kolejce w Obietnice od onmessageobsługi.

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

Odpowiedź @Michaela Yagudaeva działa dla mnie.

Ale pierwotna odpowiedź nie wiązała zapakowanej obietnicy z .catch () w celu obsługi odrzutów, oto moje ulepszenie w stosunku do odpowiedzi @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();

0

Jeśli p jest zmienną zawierającą obietnicę, p.then(empty);należy ją odrzucić, gdy w końcu się zakończy lub jeśli jest już zakończona (tak, wiem, że to nie jest oryginalne pytanie, ale jest to moje pytanie). „pusty” jest function empty() {}. Jestem tylko początkującym i prawdopodobnie się mylę, ale te inne odpowiedzi wydają się zbyt skomplikowane. Obietnice mają być proste.


0

Nadal pracuję nad tym pomysłem, ale oto jak zaimplementowałem anulowalną obietnicę za pomocą setTimeout na przykładzie.

Chodzi o to, że obietnica jest rozwiązana lub odrzucona, gdy tylko zdecydujesz, że tak jest, więc powinno być kwestią decyzji, kiedy chcesz anulować, spełnienia kryterium, a następnie reject()samodzielnego wywołania funkcji.

  • Po pierwsze, myślę, że są dwa powody, dla których warto dokończyć obietnicę wcześniej: aby ją zrealizować i z nią skończyć (co nazwałem rozwiązaniem ) oraz anulować (które nazwałem odrzuceniem ). Oczywiście, to tylko moje odczucie. Oczywiście istnieje Promise.resolve()metoda, ale znajduje się ona w samym konstruktorze i zwraca fikcyjną rozwiązaną obietnicę. Ta resolve()metoda instancji w rzeczywistości rozwiązuje instancję obiektu obietnicy.

  • Po drugie, możesz szczęśliwie dodać wszystko, co chcesz, do nowo utworzonego obiektu obietnicy, zanim go zwrócisz, dlatego właśnie dodałem resolve()i reject()metody, aby uczynić go samowystarczalnym.

  • Po trzecie, sztuczka polega na uzyskaniu dostępu do modułu wykonawczego resolvei rejectfunkcji później, więc po prostu zapisałem je w prostym obiekcie z poziomu zamknięcia.

Myślę, że rozwiązanie jest proste i nie widzę w nim większych problemów.

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>

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.