如何从setTimeout创建一个Promise

162

这并不是一个现实世界中的问题,我只是想了解一下如何创建promise。

我需要了解如何为返回空值的函数(比如setTimeout)创建promise。

假设我有以下代码:

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

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

我该如何创建一个Promise,在setTimeout准备好调用callback()后,async可以返回它?

我认为对它进行包装会让我有所进展:

function setTimeoutReturnPromise(){

    function promise(){}

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

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


    return promise;
}

但是我无法想得更远。


你是否正在尝试创建自己的 Promise 库? - T.J. Crowder
@T.J.Crowder 我之前不是这个意思,但我现在想明白了。就是想知道一个库是怎么做到的。 - laggingreflex
@ 滞后:有道理,我已经在答案中添加了一个基本的Promise实现示例。 - T.J. Crowder
3
请注意,2017年时“async”作为函数名有点令人困惑,因为你可能会写出async function async(){...} - mikemaccana
将这个问题中的 async 重命名为 later 并相应地编辑已接受的答案,以避免任何混淆,对于未来的读者是否有益呢? - Sebastian Simon
显示剩余4条评论
8个回答

219

更新(2017年)

在2017年,Promise已经内置于JavaScript中,它们是由ES2015规范添加的(对于像IE8-IE11这样的过时环境,可以使用polyfill)。他们选择的语法使用一个回调函数传递给Promise构造函数(Promise executor),该回调函数接收用于解析/拒绝承诺的函数作为参数。

首先,由于现在在JavaScript中有一个含义的async(尽管它只是在某些上下文中的关键字),我将使用later作为函数名称以避免混淆。

基本延迟

使用原生的Promise(或忠实的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返回一个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 年的原始答案

通常情况下,你会有一个承诺库(可以是自己编写的,也可以是市面上的几个库之一)。该库通常会有一个对象,你可以创建它并稍后进行“解决”,该对象将具有你可以从中获取“承诺”的“承诺”。

然后,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>

请返回翻译后的文本:http://modernjavascript.blogspot.com/2013/08/promisesa-understanding-by-doing.html相关 :) - Benjamin Gruenbaum
你的回答没有处理cancelTimeout。 - Alexander Danilov
@AlexanderDanilov:承诺是不可取消的。你当然可以编写一个函数,返回一个带有取消方法的对象,并分别为承诺提供访问器,如果调用了取消方法,则拒绝承诺... - T.J. Crowder
2
@AlexanderDanilov:我已经添加了一个。 - T.J. Crowder
1
@Leon - 前者使promise只读,因此您无法在返回的对象上分配给promise。我不清楚为什么我将其编写为访问器,而不是实际的只读数据属性。可能是懒惰-访问器具有紧凑的语法,我无法想象任何半好的JavaScript引擎不会在正常情况下优化函数调用(因为promiseconst)。将其编写为只读数据属性将更加冗长。 :-) - T.J. Crowder
显示剩余2条评论

11
一个将setTimeout包装在Promise中的单行代码。
await new Promise(r => setTimeout(r, ms))

例子:

async someFunction() {
  // Do something

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

  // Do something else
}

8
自 Node v15 版本开始,您可以使用 定时器 Promise API
文档中的示例:
import { setTimeout } from 'timers/promises'

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

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

它使用类似于浏览器fetchsignals来处理中止,有关详细信息,请参阅文档 :)

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

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

3

实现:

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

1

这不是对原问题的回答。但是,由于原问题不是一个真实世界中的问题,所以这不应该成为问题。我试图向朋友解释JavaScript中的Promise以及Promise和Callback之间的区别。

下面的代码用作说明:

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


0

最简单的方法

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

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

-1
如果以上解决方案都无法解决您的问题,请尝试这个。
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)
})()

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接