Как сделать обещание от setTimeout

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

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

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

8 ответов

Решение

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

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

Во-первых, так как async теперь имеет значение в JavaScript (хотя это только ключевое слово в определенных контекстах), я собираюсь использовать later как имя функции, чтобы избежать путаницы.

Основная задержка

Используя нативные обещания (или верный polyfill) это будет выглядеть так:

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>

Однострочник, который оборачивает обещание вокругsetTimeout

      await new Promise(r => setTimeout(r, ms))

Пример:

      async someFunction() {
  // Do something

  // Wait 2 seconds
  await new Promise(r => setTimeout(r, 2000))

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

Мы можем передать пользовательский cb fxn, как этот

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

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

      import { setTimeout } from 'timers/promises'

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

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

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

Выполнение:

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

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

Самый простой способ

      (async function() {
    console.log('1');
    
    await SleepJs(3000);
    
    console.log('2');
} )();

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

Если ни одно из этих решений не помогло вам, попробуйте это

      const asyncTimeout = (ms) => {
    // when you make a promise you have to resolve it or reject it
    // if you are like me that didn't get promises at all read the docs
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            const users = [
                { id: 1, name: 'Pablo' },
                { id: 2, name: 'Pedro' }
            ]
            resolve(users) // this returns users
        }, ms)
    })
}

(async () => {
    const obj = await asyncTimeout(3000)
    console.log(obj)
})()
Другие вопросы по тегам