如何从 setTimeout 做出承诺

Posted

技术标签:

【中文标题】如何从 setTimeout 做出承诺【英文标题】:How to make a promise from setTimeout 【发布时间】:2014-05-07 14:22:10 【问题描述】:

这不是现实世界的问题,我只是想了解 Promise 是如何创建的。

我需要了解如何为不返回任何内容的函数做出承诺,例如 setTimeout。

假设我有:

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


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

setTimeout 准备好callback() 之后,我如何创建async 可以返回的承诺?

我想包装它会带我去某个地方:

function setTimeoutReturnPromise()

    function promise()

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

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


    return promise;

但我想不出这个。

【问题讨论】:

您是否正在尝试创建您的自己的承诺库? @T.J.Crowder 我不是,但我现在想这实际上是我想要理解的。图书馆是如何做到的 @ lagging:有道理,我在答案中添加了一个基本承诺实现示例。 我认为这是一个非常现实的问题,我必须为我公司正在建设的一个大型项目解决这个问题。可能有更好的方法来做到这一点,但为了我们的蓝牙堆栈,我基本上需要延迟承诺的解决。我将在下面发布以展示我的所作所为。 请注意,在 2017 年,“异步”是一个有点令人困惑的函数名称,因为您可能有 async function async()... 【参考方案1】:

更新(2017 年)

在 2017 年,Promise 内置于 javascript,它们是由 ES2015 规范添加的(polyfills 可用于 IE8-IE11 等过时的环境)。他们使用的语法使用您传递给Promise 构造函数(Promise executor)的回调,该构造函数接收用于解析/拒绝承诺作为参数的函数。

首先,由于async now has a meaning in JavaScript(即使在某些情况下它只是一个关键字),我将使用later作为函数的名称以避免混淆。

基本延迟

使用原生 Promise(或忠实的 polyfill),它看起来像这样:

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

请注意,假设setTimeout 的版本与the definition for browsers 兼容,其中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返回一个promise,因为promise不能被取消。

但是我们可以很容易地返回一个带有cancel 方法和promise 访问器的对象,并在取消时拒绝promise:

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 年的原始答案

通常你会有一个 Promise 库(一个你自己写的,或者是其中一个)。该库通常会有一个您可以创建并稍后“解析”的对象,并且该对象将有一个您可以从中获得的“承诺”。

那么later 会看起来像这样:

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

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


在对该问题的评论中,我问:

您是否正在尝试创建自己的 Promise 库?

你说

我不是,但我现在想这实际上是我试图理解的。图书馆是怎么做的

为了帮助理解,这里是一个非常非常基本的示例,它不符合 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>

【讨论】:

modernjavascript.blogspot.com/2013/08/… 相关:) 您的答案无法处理 cancelTimeout @AlexanderDanilov:承诺不可取消。您当然可以编写一个函数,该函数返回一个带有取消方法的对象,以及一个单独的承诺访问器,然后如果取消方法被调用,则拒绝承诺...... @AlexanderDanilov:我继续添加了一个。 @Leon - 前者将promise 设为只读,因此您无法在返回的对象上分配promise。我不清楚为什么我把它写成一个访问器,而不是一个实际的只读数据属性。可能是懒惰——访问器有一个很好的紧凑语法,我无法想象任何半体面的 JavaScript 引擎在正常情况下不会优化函数调用(因为promiseconst)。将其写为只读数据属性会更加冗长。 :-)【参考方案2】:

这不是对原始问题的回答。但是,由于原始问题不是现实世界的问题,所以它不应该是问题。我试图向朋友解释什么是 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

【讨论】:

【参考方案3】:
const setTimeoutAsync = (cb, delay) =>
  new Promise((resolve) => 
    setTimeout(() => 
      resolve(cb());
    , delay);
  );

我们可以像这样传递自定义 'cb fxn' ??

【讨论】:

【参考方案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】:

从node v15开始,你可以使用timers promise API

文档中的示例:

import  setTimeout  from 'timers/promises'

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

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

它使用signals 很像浏览器fetch 来处理中止,请查看文档了解更多信息:)

【讨论】:

以上是关于如何从 setTimeout 做出承诺的主要内容,如果未能解决你的问题,请参考以下文章

js setTime()详解

是否有返回 ES6 承诺的 setTimeout 版本?

如何做出递归承诺

承诺与 setTimeout

如何捕获异步非承诺错误? (对特定错误做出反应)

在承诺链上使用 setTimeout