Як дати обіцянку з setTimeout


96

Це не реальна проблема, я просто намагаюся зрозуміти, як створюються обіцянки.

Мені потрібно зрозуміти, як дати обіцянку функції, яка нічого не повертає, як setTimeout.

Припустимо, у мене є:

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

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

Як створити обіцянку, яка asyncможе повернутися після того, як setTimeoutбуде готова callback()?

Я припускав, що загортання це кудись приведе мене:

function setTimeoutReturnPromise(){

    function promise(){}

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

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


    return promise;
}

Але я не можу думати далі цього.


Ви намагаєтесь створити власну бібліотеку обіцянок?
TJ Crowder,

@TJCrowder я не був, але, мабуть, зараз це насправді те, що я намагався зрозуміти. Ось як це зробить бібліотека
laggingreflex

@ відставання: Має сенс, я додав до відповіді приклад базової реалізації обіцянки.
TJ Crowder,

Я думаю, що це дуже реальна проблема, і яку мені довелося вирішити для масштабного проекту, який будувала моя компанія. Ймовірно, були кращі способи зробити це, але мені, по суті, потрібно було відкласти вирішення обіцянки заради нашого стека Bluetooth. Я розміщу нижче, щоб показати, що я зробив.
sunny-mittal

1
Тільки примітка, що в 2017 році `` асинхронізація '' є дещо заплутаною назвою функції, як ви могли б матиasync function async(){...}
mikemaccana

Відповіді:


129

Оновлення (2017)

Ось у 2017 році Promises вбудовані в JavaScript, вони були додані специфікацією ES2015 (полізаповнення доступні для застарілих середовищ, таких як IE8-IE11). Синтаксис, з яким вони пішли, використовує зворотний виклик, який ви передаєте в Promiseконструктор ( Promise виконавець ), який отримує функції для вирішення / відхилення обіцянки як аргументи.

По-перше, оскільки asyncзараз значення має значення в JavaScript (хоча це лише ключове слово у певному контексті), я буду використовувати його laterяк назву функції, щоб уникнути плутанини.

Основна затримка

Використовуючи рідні обіцянки (або вірний поліфіл), це буде виглядати так:

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

Зверніть увагу, що передбачається версія, setTimeoutяка відповідає визначенню для браузерів, де setTimeoutне передаються аргументи зворотного виклику, якщо ви не надаєте їх після інтервалу (це може бути неправдою в не браузерних середовищах і раніше не було правда для Firefox, але зараз; це справедливо для Chrome і навіть для IE8).

Базова затримка зі значенням

Якщо ви хочете, щоб ваша функція необов’язково передавала значення роздільної здатності, у будь-якому неясно сучасному браузері, який дозволяє вам надати додаткові аргументи setTimeoutпісля затримки, а потім передає їх до зворотного виклику при виклику, ви можете це зробити (поточні Firefox та Chrome; IE11 + , імовірно Edge; не IE8 або IE9, не маю уявлення про 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);
        */
    });
}

Якщо ви використовуєте функції ES2015 + зі стрілками, це може бути більш стисло:

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

або навіть

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

Скасовувана затримка зі значенням

Якщо ви хочете зробити можливим скасувати час очікування, ви не можете просто повернути обіцянку від later, оскільки обіцянки не можна скасувати.

Але ми можемо легко повернути об'єкт із cancelметодом та засобом доступу до обіцянки та відхилити обіцянку після скасування:

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


Оригінальна відповідь від 2014 року

Зазвичай у вас є бібліотека обіцянок (та, яку ви пишете самі, або одна з кількох там). У цій бібліотеці, як правило, є об’єкт, який ви можете створити, а згодом «вирішити», і цей об’єкт матиме «обіцянку», яку ви можете отримати від нього.

Тоді later, як правило, виглядатиме приблизно так:

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

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

У коментарі до запитання я запитав:

Ви намагаєтесь створити власну бібліотеку обіцянок?

і ти сказав

Я не був, але, мабуть, зараз саме це я і намагався зрозуміти. Це те, як це зробила б бібліотека

Щоб допомогти цьому зрозуміти, ось дуже простий приклад, який не віддалено відповідає Promises-A: Live Copy

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


ваша відповідь не справляється з cancelTimeout
Олександр Данилов

@AlexanderDanilov: Обіцянки не можна скасувати. Ви, звичайно, можете написати функцію, яка повертає об'єкт із методом скасування та окремо - аксесуаром для обіцянки, а потім відхиляє обіцянку, якщо метод скасування був викликаний ...
TJ Crowder,

1
@AlexanderDanilov: Я пішов і додав одного.
TJ Crowder,

0

Це не відповідь на вихідне запитання. Але, оскільки оригінальне питання не є реальною проблемою, воно не повинно бути проблемою. Я намагався пояснити другові, що таке обіцянки в JavaScript та різниця між обіцянкою та зворотним дзвінком.

Поясненням служить наведений нижче код.

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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.