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ć.

dx_over_dt
źródło
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 .

Bergi
źródło
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

Michael Yagudaev
źródło
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 });
Pho3nixHun
źródło
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();
Slava M
źródło
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);
nikksan
źródło
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

Cyril CHAPON
źródło
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

Sohail
źródło
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')
})
Shimon Doodkin
źródło
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();
});
Devi
źródło
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

vlio20
źródło
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
DanLatimer
źródło
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;
}

AnthumChris
źródło
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();

źródło
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.

David Spector
źródło
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>

Manngo
źródło