Как выполнить обещание из setTimeout

#javascript #jquery

#javascript #время установки #обещаю

Вопрос:

Это не проблема реального мира, я просто пытаюсь понять, как создаются обещания.

Мне нужно понять, как выполнить обещание для функции, которая ничего не возвращает, например, 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;
}
 

Но я не могу думать дальше этого.

Комментарии:

1. Вы пытаетесь создать свою собственную библиотеку обещаний?

2. @T.J.Crowder Я не был, но я думаю, что теперь это на самом деле то, что я пытался понять. Вот как библиотека будет это делать

3. @ lagging: имеет смысл, я добавил пример базовой реализации обещания к ответу.

4. Я думаю, что это очень реальная проблема, которую мне пришлось решать для масштабного проекта, который создавала моя компания. Вероятно, были лучшие способы сделать это, но мне, по сути, нужно было отложить разрешение обещания ради нашего стека Bluetooth. Я опубликую ниже, чтобы показать, что я сделал.

5. Просто обратите внимание, что в 2017 году «async» — это несколько запутанное имя для функции, как у вас может быть async function async(){...}

Ответ №1:

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

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

Во-первых, поскольку async now имеет значение в 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 arrow, это может быть более кратким:

 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
}
 

В комментарии к вопросу я спросил:

Вы пытаетесь создать свою собственную библиотеку обещаний?

и вы сказали

Я не был, но я думаю, что теперь это на самом деле то, что я пытался понять. Вот как библиотека будет это делать

Чтобы помочь этому пониманию, вот очень простой пример, который не соответствует удаленным обещаниям: живая копия

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

Комментарии:

1. modernjavascript.blogspot.com/2013/08 /… связанные 🙂

2. ваш ответ не обрабатывает cancelTimeout

3. @AlexanderDanilov: Обещания не отменяются. Вы, конечно, могли бы написать функцию, которая возвращала объект с помощью метода cancel и, отдельно, средства доступа для promise, а затем отклонить обещание, если был вызван метод cancel …

4. @AlexanderDanilov: Я пошел дальше и добавил один.

5. @Leon — первый делает promise доступным только для чтения, поэтому вы не можете назначить promise его на объект, который вы получаете обратно. Мне непонятно, почему я написал его как средство доступа, а не как фактическое свойство данных только для чтения. Вероятно, лень — средство доступа имеет приятный компактный синтаксис, и я не могу представить, чтобы какой-либо полу-приличный движок JavaScript не оптимизировал бы вызов функции в обычном случае (поскольку promise is const ). Запись его как свойства данных, доступного только для чтения, была бы гораздо более подробной. 🙂

Ответ №2:

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

Мы можем передать пользовательский ‘cb fxn’, подобный этому 👆🏽

Ответ №3:

Начиная с node v15, вы можете использовать API обещаний таймеров

пример из документа:

 import { setTimeout } from 'timers/promises'

const res = await setTimeout(100, 'result')

console.log(res)  // Prints 'result'
 

Он использует signals очень похожий браузер fetch для обработки прерывания, подробнее см. Документ 🙂

Ответ №4:

Реализация:

 // Promisify setTimeout
const pause = (ms, cb, ...args) =>
  new Promise((resolve, reject) => {
    setTimeout(async () => {
      try {
        resolve(await cb?.(...args))
      } catch (error) {
        reject(error)
      }
    }, ms)
  })
 

Тесты:

 // Test 1
pause(1000).then(() => console.log('called'))
// Test 2
pause(1000, (a, b, c) => [a, b, c], 1, 2, 3).then(value => console.log(value))
// Test 3
pause(1000, () => {
  throw Error('foo')
}).catch(error => console.error(error))
 

Ответ №5:

Это не ответ на первоначальный вопрос. Но, поскольку исходный вопрос не является реальной проблемой, он не должен быть проблемой. Я попытался объяснить другу, что такое обещания в 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