Jak złożyć obietnicę z setTimeout


96

To nie jest prawdziwy problem, po prostu próbuję zrozumieć, jak powstają obietnice.

Muszę zrozumieć, jak złożyć obietnicę dotyczącą funkcji, która nic nie zwraca, na przykład setTimeout.

Załóżmy, że mam:

function async(callback){ 
    setTimeout(function(){
        callback();
    }, 5000);
}

async(function(){
    console.log('async called back');
});

Jak utworzyć obietnicę, która asyncmoże zostać zwrócona, gdy setTimeoutbędzie gotowa callback()?

Przypuszczałem, że zawinięcie go gdzieś zabierze:

function setTimeoutReturnPromise(){

    function promise(){}

    promise.prototype.then = function() {
        console.log('timed out');
    };

    setTimeout(function(){
        return ???
    },2000);


    return promise;
}

Ale nie mogę myśleć poza tym.


Czy próbujesz stworzyć własną bibliotekę obietnic?
TJ Crowder

@TJCrowder Nie byłem, ale myślę, że teraz właśnie to próbowałem zrozumieć. Tak zrobiłaby biblioteka
opóźniony refleks

@ lagging: To ma sens, dodałem do odpowiedzi przykład podstawowej implementacji obietnicy.
TJ Crowder

Myślę, że jest to bardzo realny problem, który musiałem rozwiązać w związku z ogromnym projektem, który budowała moja firma. Prawdopodobnie istniały lepsze sposoby, aby to zrobić, ale zasadniczo musiałem opóźnić rozwiązanie obietnicy ze względu na nasz stos bluetooth. Napiszę poniżej, aby pokazać, co zrobiłem.
sunny-mittal

1
Tylko uwaga, że ​​w 2017 async function async(){...}
``

Odpowiedzi:


132

Aktualizacja (2017)

Tutaj w 2017 obietnice są wbudowane w JavaScript, zostały dodane zgodnie ze specyfikacją ES2015 (wypełniacze są dostępne dla przestarzałych środowisk, takich jak IE8-IE11). Składnia, z którą poszli, wykorzystuje wywołanie zwrotne, które przekazujesz do Promisekonstruktora ( Promise executora ), który otrzymuje funkcje do rozwiązywania / odrzucania obietnicy jako argumenty.

Po pierwsze, ponieważ asyncteraz ma znaczenie w JavaScript (mimo że jest to słowo kluczowe tylko w niektórych kontekstach), zamierzam użyć laterjako nazwy funkcji, aby uniknąć nieporozumień.

Podstawowe opóźnienie

Używając natywnych obietnic (lub wiernego polyfill), wyglądałoby to tak:

function later(delay) {
    return new Promise(function(resolve) {
        setTimeout(resolve, delay);
    });
}

Zauważ, że zakłada się, że wersja setTimeoutjest zgodna z definicją dla przeglądarek, w której setTimeoutnie przekazuje żadnych argumentów do wywołania zwrotnego, chyba że podasz je po interwale (może to nie być prawdą w środowiskach innych niż przeglądarka i nie było prawda w Firefoksie, ale jest teraz; jest prawdą w Chrome, a nawet w IE8).

Podstawowe opóźnienie z wartością

Jeśli chcesz, aby funkcja opcjonalnie przekazywała wartość rozdzielczości, w dowolnej dość nowoczesnej przeglądarce, która umożliwia podanie dodatkowych argumentów setTimeoutpo opóźnieniu, a następnie przekazanie ich do wywołania zwrotnego po wywołaniu, możesz to zrobić (bieżące przeglądarki Firefox i Chrome; IE11 + , prawdopodobnie Edge; nie IE8 ani IE9, nie mam pojęcia o IE10):

function later(delay, value) {
    return new Promise(function(resolve) {
        setTimeout(resolve, delay, value); // Note the order, `delay` before `value`
        /* Or for outdated browsers that don't support doing that:
        setTimeout(function() {
            resolve(value);
        }, delay);
        Or alternately:
        setTimeout(resolve.bind(null, value), delay);
        */
    });
}

Jeśli używasz funkcji strzałek ES2015 +, może to być bardziej zwięzłe:

function later(delay, value) {
    return new Promise(resolve => setTimeout(resolve, delay, value));
}

lub nawet

const later = (delay, value) =>
    new Promise(resolve => setTimeout(resolve, delay, value));

Opóźnienie z możliwością anulowania z wartością

Jeśli chcesz umożliwić anulowanie limitu czasu, nie możesz po prostu zwrócić obietnicy od later, ponieważ obietnic nie można anulować.

Ale możemy łatwo zwrócić obiekt z cancelmetodą i akcesorium dla obietnicy i odrzucić obietnicę w przypadku anulowania:

const later = (delay, value) => {
    let timer = 0;
    let reject = null;
    const promise = new Promise((resolve, _reject) => {
        reject = _reject;
        timer = setTimeout(resolve, delay, value);
    });
    return {
        get promise() { return promise; },
        cancel() {
            if (timer) {
                clearTimeout(timer);
                timer = 0;
                reject();
                reject = null;
            }
        }
    };
};

Przykład na żywo:

const later = (delay, value) => {
    let timer = 0;
    let reject = null;
    const promise = new Promise((resolve, _reject) => {
        reject = _reject;
        timer = setTimeout(resolve, delay, value);
    });
    return {
        get promise() { return promise; },
        cancel() {
            if (timer) {
                clearTimeout(timer);
                timer = 0;
                reject();
                reject = null;
            }
        }
    };
};

const l1 = later(100, "l1");
l1.promise
  .then(msg => { console.log(msg); })
  .catch(() => { console.log("l1 cancelled"); });

const l2 = later(200, "l2");
l2.promise
  .then(msg => { console.log(msg); })
  .catch(() => { console.log("l2 cancelled"); });
setTimeout(() => {
  l2.cancel();
}, 150);


Oryginalna odpowiedź z 2014 r

Zwykle będziesz mieć bibliotekę obietnic (taką, którą napiszesz sam, lub jedną z kilku dostępnych). Ta biblioteka zazwyczaj będzie zawierała obiekt, który możesz utworzyć i później „rozwiązać”, a obiekt ten będzie miał „obietnicę”, którą możesz z niego uzyskać.

Wtedy laterwyglądałby mniej więcej tak:

function later() {
    var p = new PromiseThingy();
    setTimeout(function() {
        p.resolve();
    }, 2000);

    return p.promise(); // Note we're not returning `p` directly
}

W komentarzu do pytania zapytałem:

Czy próbujesz stworzyć własną bibliotekę obietnic?

i Ty powiedziałeś

Nie byłem, ale myślę, że teraz właśnie to próbowałem zrozumieć. Tak zrobiłaby biblioteka

Aby pomóc w zrozumieniu, oto bardzo podstawowy przykład, który nie jest zdalnie zgodny z obietnicą A: kopia na żywo

<!DOCTYPE html>
<html>
<head>
<meta charset=utf-8 />
<title>Very basic promises</title>
</head>
<body>
  <script>
    (function() {

      // ==== Very basic promise implementation, not remotely Promises-A compliant, just a very basic example
      var PromiseThingy = (function() {

        // Internal - trigger a callback
        function triggerCallback(callback, promise) {
          try {
            callback(promise.resolvedValue);
          }
          catch (e) {
          }
        }

        // The internal promise constructor, we don't share this
        function Promise() {
          this.callbacks = [];
        }

        // Register a 'then' callback
        Promise.prototype.then = function(callback) {
          var thispromise = this;

          if (!this.resolved) {
            // Not resolved yet, remember the callback
            this.callbacks.push(callback);
          }
          else {
            // Resolved; trigger callback right away, but always async
            setTimeout(function() {
              triggerCallback(callback, thispromise);
            }, 0);
          }
          return this;
        };

        // Our public constructor for PromiseThingys
        function PromiseThingy() {
          this.p = new Promise();
        }

        // Resolve our underlying promise
        PromiseThingy.prototype.resolve = function(value) {
          var n;

          if (!this.p.resolved) {
            this.p.resolved = true;
            this.p.resolvedValue = value;
            for (n = 0; n < this.p.callbacks.length; ++n) {
              triggerCallback(this.p.callbacks[n], this.p);
            }
          }
        };

        // Get our underlying promise
        PromiseThingy.prototype.promise = function() {
          return this.p;
        };

        // Export public
        return PromiseThingy;
      })();

      // ==== Using it

      function later() {
        var p = new PromiseThingy();
        setTimeout(function() {
          p.resolve();
        }, 2000);

        return p.promise(); // Note we're not returning `p` directly
      }

      display("Start " + Date.now());
      later().then(function() {
        display("Done1 " + Date.now());
      }).then(function() {
        display("Done2 " + Date.now());
      });

      function display(msg) {
        var p = document.createElement('p');
        p.innerHTML = String(msg);
        document.body.appendChild(p);
      }
    })();
  </script>
</body>
</html>


twoja odpowiedź nie dotyczy cancelTimeout
Alexander Danilov

@AlexanderDanilov: Obietnic nie można anulować. Z pewnością możesz napisać funkcję, która zwróci obiekt z metodą anulowania i osobno akcesorium dla obietnicy, a następnie odrzuć obietnicę, jeśli metoda anulowania została wywołana ...
TJ Crowder

1
@AlexanderDanilov: Poszedłem dalej i dodałem jeden.
TJ Crowder

1
const setTimeoutAsync = (cb, delay) =>
  new Promise((resolve) => {
    setTimeout(() => {
      resolve(cb());
    }, delay);
  });

Możemy przekazać niestandardowe 'cb fxn' takie jak ten 👆🏽


0

To nie jest odpowiedź na pierwotne pytanie. Ale ponieważ oryginalne pytanie nie jest problemem w świecie rzeczywistym, nie powinno być problemem. Próbowałem wyjaśnić znajomemu, czym są obietnice w JavaScript i jaka jest różnica między obietnicą a wywołaniem zwrotnym.

Poniższy kod służy jako wyjaśnienie:

//very basic callback example using setTimeout
//function a is asynchronous function
//function b used as a callback
function a (callback){
    setTimeout (function(){
       console.log ('using callback:'); 
       let mockResponseData = '{"data": "something for callback"}'; 
       if (callback){
          callback (mockResponseData);
       }
    }, 2000);

} 

function b (dataJson) {
   let dataObject = JSON.parse (dataJson);
   console.log (dataObject.data);   
}

a (b);

//rewriting above code using Promise
//function c is asynchronous function
function c () {
   return new Promise(function (resolve, reject) {
     setTimeout (function(){
       console.log ('using promise:'); 
       let mockResponseData = '{"data": "something for promise"}'; 
       resolve(mockResponseData); 
    }, 2000);      
   }); 

}

c().then (b);

JsFiddle

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.