【问题标题】:How to reuse promises?如何重用承诺?
【发布时间】:2015-10-03 23:12:46
【问题描述】:

我试图在这里重用从 promise 返回的数据。但是,问题是,在第一次调用checkPromise 函数之后,它立即调用了第二个函数,并且第一个函数的承诺没有实现,所以它永远不会返回任何数据,因此它永远不会进入 if 子句。如何重用 Promise?

var Promise = require('bluebird');
var request = Promise.promisify(require("request"));


var url = 'http://www.google.com';
var obj = new Object;

function apiCall(url) {
    return new Promise(function (resolve, reject) {

        request(url).spread(function(response, body) {
            return resolve(body);
        }).catch(function(err) {
            console.error(err);
            return reject(err);
        });

    });
}

function checkPromise(url) {
    if(obj.hasOwnProperty(url)) {   
        var rp = obj[url];
        //do something
    }
    else {
        apiCall(url).then(function(result) {            
            obj[url] = result; 
            //do something
        });
    }
}

checkPromise(url);
checkPromise(url);

【问题讨论】:

  • 为什么不将结果存储在闭包中?
  • 承诺不能被“重用”。它成功(一次),或者失败(一次),或者都没有完成。这实际上非常好。使用辅助机制来保存数据。在任何情况下,promise 看起来都是非异步使用的(即,promise 在第二次调用时not 尚未完成),这违背了promise 的基本性质。 (将函数设置为“可能是异步的”,因为它releases Zalgo 通常也很糟糕)。

标签: javascript node.js promise bluebird


【解决方案1】:

Here 是一个最简单的例子,如果有多个类似的请求(例如缓存检查),如何防止多个 API 调用

var _cache = {
    state: 0,
    result: undefined,
    getData: function(){
    log('state: ' + this.state);
    if(this.state === 0 ){ // not started
        this.state = 1; // pending
      this.promise = new Promise(function(resolve, reject) {
        return (apiCall().then(data => { _cache.result = data; _cache.state = 2; resolve(_cache.result) }));
      })

      return this.promise;
    }
    else if(this.state === 1){ // pending
        return this.promise;
    }
    else if(this.state === 2){// resolved
        return Promise.resolve(this.result);
    }
  },
};

模拟api调用

function apiCall(){
    return new Promise(function(resolve, reject) {
    log('in promise')

    setTimeout(() => {
        log('promise resolving')
        resolve(1);
    }, 1000);
  })
}

同时发出请求。

_cache.getData().then(result => { log('first call outer: ' + result);
    _cache.getData().then(result => { log('first call inner: ' + result); });
});

_cache.getData().then(result => { log('second call outer: ' + result);
    _cache.getData().then(result => { log('second call inner: ' + result); });
});

只进行一次 API 调用。所有其他人将等待完成或使用已解决的结果(如果已完成)。

【讨论】:

    【解决方案2】:

    您可能遇到了时间问题。您的 apiCall() 函数是异步的。这意味着它会在稍后的某个时间完成。因此,每次您拨打checkPromise() 时,您所做的只是开始一个请求,并在稍后的某个时间完成。因此,您第一次调用它并启动一个请求(尚未完成)。然后,您对checkPromise() 的下一次调用会被调用,它会在第一次调用完成之前进行if 检查。因此,它还没有在缓存中找到任何内容。

    您的代码并行运行两个请求,而不是一个接一个。

    如果您真的想等到第一个请求完成后再执行第二个请求,那么您必须实际构建代码来执行此操作。您需要让 checkPromise() 自己返回一个 Promise,以便使用它的代码知道它何时实际完成,以便在完成后执行某些操作。

    仅供参考,我在您的代码中看不到任何与重用 promise 相关的内容(这是您无法做到的,因为它们是一次性对象)。

    这是一种可能的实现方式:

    var Promise = require('bluebird');
    var request = Promise.promisify(require("request"));
    
    var url = 'http://www.google.com';
    var obj = {};
    
    function apiCall(url) {
        return request(url).spread(function(response, body) {
            return body;
        });
    }
    
    function checkPromise(url) {
        if(obj.hasOwnProperty(url)) {   
            var rp = obj[url];
            //do something
            return Promise.resolve(rp);
        }
        else {
            return apiCall(url).then(function(result) {            
                obj[url] = result; 
                //do something
                return result;
            });
        }
    }
    
    checkPromise(url).then(function() {
        checkPromise(url);
    });
    

    重大变化:

    1. 返回由 request() 返回的承诺,而不是创建另一个承诺。
    2. 更改checkPromise(),使其始终返回一个承诺,无论该值是否在缓存中找到,因此调用代码始终可以始终如一地工作。
    3. 对两个 checkPromise() 调用进行排序,以便第一个调用可以在第二个调用之前完成。

    如果您感兴趣的结果已经被加载,一个非常不同的方法是实际等待缓存。可以这样做:

    var Promise = require('bluebird');
    var request = Promise.promisify(require("request"));
    
    var url = 'http://www.google.com';
    var obj = {};
    
    function apiCall(url) {
        return request(url).spread(function(response, body) {
            return body;
        });
    }
    
    function checkPromise(url) {
        if(obj.hasOwnProperty(url)) {   
            // If it's a promise object in the cache, then loading 
            // If it's a value, then the value is already available
            // Either way, we wrap it in a promise and return that
            return Promise.resolve(obj[url]);
        } else {
            var p = apiCall(url).then(function(result) {
                obj[url] = result; 
                //do something
                return result;
            });
            obj[url] = p;
            return p;
        }
    }
    
    checkPromise(url).then(function(result) {
        // use result
    });
    
    checkPromise(url).then(function(result) {
        // use result
    });
    

    【讨论】:

    • 添加了更改的代码示例,不仅允许对 checkPromise() 调用进行排序,而且还删除了几个反模式以提高编程一致性并删除不必要的承诺。
    • 第二个例子可以简化。 API 调用返回的 Promise 应该是可信的 Promise。如果您使用受信任的承诺调用 Promise.resolve,它只会返回该承诺。这意味着您可以省略 typeof 检查并简单地返回 Promise.resolve(rp);
    • @AdamvandenHoven - 我实现了你的建议。
    【解决方案3】:

    鉴于您将结果全局存储在 'obj[url]' 中的方式,这可能是最容易做到的

    function checkPromise(url) {
        if (!obj[url]) obj[url] = apiCall(url);
    
        obj[url].then(function(result) {
            //do something
        });
    }
    

    基本上发出请求,如果它还没有开始,然后在结果加载时附加一个监听器。

    【讨论】:

      【解决方案4】:

      你的代码很少有问题,首先在apiCall,你正在做一个promise ant-pattern(不需要那个新的promise),其次你的checkPromise 正在做一个同步操作,所以它必须要么返回一个promise,要么有回调参数,因此您的代码可以更改为:

      var Promise = require('bluebird');
      var request = Promise.promisify(require("request"));
      
      
      var url = 'http://www.google.com';
      var obj = new Object;
      
      
      function apiCall(url) {
      
          return request(url).spread(function(response, body) {
              return body;
          }).catch(function(err) {
              console.error(err);
              throw err;
          });
      }
      
      
      function checkPromise(url) {
          var promise = Promise.resolve();
          if(obj.hasOwnProperty(url)) {   
              var rp = obj[url];
              //do something
          }
          else {
              return apiCall(url).then(function(result) {            
                  obj[url] = result; 
                  //do something
              });
          }
      
          return promise;
      
      }
      
      checkPromise(url).then(function(){
          return checkPromise(url);
      });
      

      【讨论】:

      • 也像 jfriend00 说的,我不确定这与重用 Promise 有什么关系。
      猜你喜欢
      • 2017-01-30
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-12-17
      • 1970-01-01
      • 1970-01-01
      • 2015-05-01
      • 2016-07-27
      相关资源
      最近更新 更多