Как дать обещание из 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
@ lagging: имеет смысл, я добавил в ответ пример базовой реализации обещания.
TJ Crowder
Я думаю, что это очень реальная проблема, и мне пришлось решить ее для масштабного проекта, который строила моя компания. Вероятно, были более эффективные способы сделать это, но мне по сути нужно было отложить выполнение обещания ради нашего стека bluetooth. Я опубликую ниже, чтобы показать, что я сделал.
sunny-mittal
1
Замечу, что в 2017 году async - это несколько сбивающее с толку имя функции, как и у вас,async function async(){...}
mikemaccana

Ответы:

132

Обновление (2017 г.)

Здесь, в 2017 году, обещания встроены в 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>
TJ Crowder
источник
modernjavascript.blogspot.com/2013/08/… связаны :)
Бенджамин Грюнбаум
ваш ответ не обрабатывает cancelTimeout
Александр Данилов
@AlexanderDanilov: Обещания не подлежат отмене. Вы, конечно, могли бы написать функцию, которая возвращала бы объект с методом отмены и отдельно аксессором для обещания, а затем отклонить обещание, если был вызван метод отмены ...
TJ Crowder
1
@AlexanderDanilov: Я пошел дальше и добавил.
TJ Crowder
1
const setTimeoutAsync = (cb, delay) =>
  new Promise((resolve) => {
    setTimeout(() => {
      resolve(cb());
    }, delay);
  });

Мы можем передать собственный 'cb fxn', как этот 👆🏽

CodeFinity
источник
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

Юрин
источник