How to reuse promises?

醉酒当歌 提交于 2019-12-04 12:10:41

You likely have a timing issue. Your apiCall() function is asynchronous. That means it finishes sometime later. As such, each time you call checkPromise(), all you're doing is starting a request and it finishes sometime later. So, you call it the first time and it starts a request (that has not finished yet). Then, your next call to checkPromise() gets called and it does it's if check before the first call has completed. Thus, it finds nothing in the cache yet.

Your code is running two requests in parallel, not one after the other.

If you actually want to wait until the first request is done before executing the second one, then you will have to actually structure your code to do that. You would need to make checkPromise() return a promise itself so code using it could known when it was actually done in order to execute something after it was done.

FYI, I don't see anything in your code that is actually related to reusing promises (which is something you cannot do because they are one-shot objects).

Here's one possible implementation:

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);
});

Significant changes:

  1. Return the promise returned by request() rather than create yet another one.
  2. Change checkPromise() so it always returns a promise whether the value is found in the cache or not so calling code can always work consistently.
  3. Sequence the two checkPromise() calls so the first can finish before the second is executed.

A very different approach would be to actually wait on the cache if a result you are interested in is already being loaded. That could be done like this:

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
});

few problems with your code, first in apiCall, you are doing a promise ant-pattern( no need for that new promise), second your checkPromise is doing a sync operation, so it must either return a promise or have a callback argument, so you code can be changed into:

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);
});

Given the way you are globally storing the result in 'obj[url]', it'd probably be easiest to do

function checkPromise(url) {
    if (!obj[url]) obj[url] = apiCall(url);

    obj[url].then(function(result) {
        //do something
    });
}

to basically make the request, if it hasn't already started, then attach a listener to the promise for when the result has loaded.

Here is the simplest example of how to prevent multiple API calls if there are multiple similar request for something (cache check for example)

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);
    }
  },
};

Simulating api call

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

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

Making simultaneous requests.

_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); });
});

Only one API call is maden. All others will wait for completion or use the resolved result if it already completed.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!