I have a small library with a single API function, start().
Once started, it should check a URL every 2 seconds and after some time the url-checker will resolve.
But I don't know how to implement the repeated setTimeout for a deferred function..I tried variations where the checkProgress() calls itself but then the promise isn't returned anymore.
Here's the code:
Lib.progressChecker = (function($) {
'use strict';
var api = {};
var checkProgress = function (url) {
var d = $.Deferred();
$.get(url).done(function(foo) {
if (foo === 'bar') {
//script is not finished yet
} else {
//finished, resolve and stop looping
d.resolve();
}
});
return d.promise();
};
api.start = function(projectId) {
var url = 'foobar/'+projectId;
var d = $.Deferred();
setTimeout(function(){
checkProgress(url).done(function () {
d.resolve();
});
}, 2000);
return d.promise();
};
return api;
}) (jQuery);
You can do it like this where you just resolve the first deferred when you see the $.get() returns the desired value and if not, you run it again:
Lib.progressChecker = (function($) {
'use strict';
var api = {};
api.start = function(projectId) {
var url = 'foobar/'+projectId;
var d = $.Deferred();
function next() {
setTimeout(function(){
$.get(url).then(function(foo) {
if (foo === 'bar') {
// continue checking
next();
} else {
// done now
d.resolve(foo);
}
}, function(err) {
// error so stop
// don't want to forever loop in an error condition
d.reject(err);
});
}, 2000);
}
next();
return d.promise();
};
return api;
}) (jQuery);
FYI, if you control the server end of things here, it looks like an ideal situation for a webSocket where, rather than polling every two seconds, you can tell the server you want to be notified when things change and the server can just tell you when something changes on the server side.
Related
I wish to call dealCardSelectableAI(), have it chooseCards(), then use the output to set an observable system.star.cardList(), then call setCardName(). Once all this is done I want saveGame() to execute.
However, setCardName() is not completing before saveGame() is called, so apparently I can't push it into my deferredQueue via a .then().
I'm using jQuery due to working within an ES5 environment.
var setCardName = function (system, card) {
var deferred = $.Deferred();
require(["cards/" + card[0].id], function (data) {
var cardName = loc(data.summarize());
system.star.ai().cardName = cardName;
deferred.resolve();
});
return deferred.promise();
};
var dealCardSelectableAI = function (win, turnState) {
var deferred = $.Deferred();
// Avoid running twice after winning a fight
if (!win || turnState === "end") {
var deferredQueue = [];
_.forEach(model.galaxy.systems(), function (system, starIndex) {
if (
model.canSelect(starIndex) &&
system.star.ai() &&
system.star.ai().treasurePlanet !== true
) {
deferredQueue.push(
chooseCards({
inventory: inventory,
count: 1,
star: system.star,
galaxy: game.galaxy(),
addSlot: false,
}).then(function (card) {
system.star.cardList(card);
deferredQueue.push(setCardName(system, card));
})
);
}
});
$.when(deferredQueue).then(function () {
deferred.resolve();
});
} else {
deferred.resolve();
}
return deferred.promise();
};
dealCardSelectableAI(false).then(function () {
saveGame(game, true);
});
I tried resolving this by changing the function calls so setCardName() was chained following dealCardSelectableAI(). However, it relies on system.star.cardList() having been written, which in some circumstances had not yet been done.
Given the dependency system.star.cardList() has on chooseCards(), I cannot figure out how to make sure it has been written to before calling setCardName() in a way which blocks saveGame() until everything is done.
I have a save function in my app which can be called manually and an autosave function which runs every 60 seconds.
To prevent the two ops trying to access the same file at the same instant, I set a flag called isSaving to true when one starts running, and to false again afterward. If open or save detect that autosave is running, they wait 1000ms and try again. If they fail after that I consider it an error.
Autosave:
setInterval(autosave, 1000 * 60);
isSaving = false;
function autosave()
{
return new WinJS.Promise(function (complete, error, progress)
{
if(isSaving == false) // no saving op in progress
{
// set saving flag on
isSaving = true;
// write file
return writeFile(currentFile)
.then(function () {
// saving flag off
isSaving = false;
complete();
});
}
else {
// silently abort
complete();
}
});
}
Manual save:
var saveFileAttempts = 0;
function save()
{
return new WinJS.Promise(function (complete, error, progress)
{
if (isSaving == false) // no saving op in progress
{
// set saving flag on
isSaving = true;
// write file
return writeFile(currentFile)
.then(function () {
// show notification to user "file saved"
return showSaveNotification()
})
.then(function () {
// set saving flag off
isSaving = false;
complete();
});
}
else if (saveFileAttempts < 10) {
// try again in 1000ms, up to 10 times
saveFileAttempts++;
setTimeout(function () { save(); }, 1000);
}
else{
error();
}
});
}
Open:
var openFileAttempts = 0;
function open()
{
return new WinJS.Promise(function (complete, error, progress)
{
if (isSaving == false)
{
return readFile()
.then(function (file) {
currentFile = file;
openFileAttempts = 0;
complete();
});
}
else if (openFileAttempts < 10) {
// try again in 1000ms, up to 10 times
openFileAttempts++;
setTimeout(function () { open(); }, 1000);
}
else{
error();
}
});
}
This feels like a hack. Is there a better way to achieve what I'm trying to do?
FYI: These functions return promises because there are other functions that call them.
Instead of waiting 1000ms and trying again, I'd recommend using a promise to represent that a save is ongoing and when it will end.
var saving = null;
setInterval(function() {
if (!saving) // ignore autosave when already triggered
save().then(showAutoSafeNotification);
}, 60e3);
function save() {
if (saving)
return saving.then(save); // queue
// else
var written = writeFile(currentFile);
saving = written.then(function() {
saving = null;
}, function() {
saving = null;
});
return written;
}
You can do the same with open (and might want to abstract the written part out), although I fail to see how it interferes with an (auto)save. If you're concerned about reading the file that is already open while it is saved, I'd let the filesystem handle that and catch the error.
how about maintaining a single promise chain. Then, you might not need a setTimeout, this is a easy way, might be flawed, haven't used WinJS, writing code like it is normal promise:
setInterval(save, 1000 * 60);
var promise = Promise.resolve();
function save(){
return promise.then(function(){
return writeFile(currentFile);
});
}
function open(){
return promise.then(function(){
return readFile().then(function (file) {
currentFile = file;
});
});
}
but I guess, one problem with this code is, since it is single promise chain, you need to catch error properly in your application.
I've been using Bluebird a lot recently on a HAPI API development. I've just run into my first real problem, that perhaps my understanding or naivety has me stumped.
The following code is an example of what I am facing:-
var Promise = require('bluebird'),
stuff = require('../stuff');
module.exports = {
getSomething: function(request, reply) {
var p = Promise.resolve();
p = p.then(function() {
return db.find() //etc. etc.
});
p = p.then(function(resultFromPromise) {
//problems begin here
var data = stuff.doSomeReallyLongAndBoringFunction(resultFromPromise);
return data;
});
p.then(function(data) {
//no data here.
});
};
};
I've commented where the problems usually begin. the stuff.doSomeReallyLongAndBoringFunction() returns an object (using more promises concidently) and it's this object I want to access, but //no data here always fires before data returns. stuff.doSomeReallyLongAndBoringFunction() continues to run regardless and completes successfully, but after the code goes async, I don't know how to promise that function's return value back.
Can anyone offer any guidance? Please accept my apologies for any naivety in the question!
Help as always, is appreciated
NB just for clarity, stuff.doSomeReallyLongAndBoringFunction() does not return a Promise itself. Although, I did try return new Promise(reject, resolve) { }); manual wrap. It is simply a function that uses promises itself (successfully) to get data.
Update 1
stuff.doSomeReallyLongAndBoringFunction() is too big to post directly, but it does something like this:-
var Promise = require('bluebird'),
rp = require('request-promise');
module.exports = {
doSomeReallyLongAndBoringFunction: function() {
var p = Promise.resolve();
p = p.then(function() {
return db.find() //etc. etc.
});
p.then(function() {
rp(options).then(function(response){
//get some data from remote location
}).then(function(dataFromService) {
//do some jiggery pokery with said data
var marshalledData = dataFromService;
db.something.create({
Field: 'something'
}).exec(function(err, saved) {
return marshalledData;
});
});
}).catch(function(err) {
});
};
};
Update 2
Thank you Justin for your help. Here is the actual code, perhaps this may help?
Promise.resolve()
.then(function() {
if(typeof utils.intTryParse(place) !== 'number') {
return foursquare.createPlaceFromFoursquare(sso, place, request, reply);
} else {
return { Place: { PlaceId: place }};
}
}).then(function(placeObj) {
console.log('Place set as', placeObj); //always returns undefined, despite function actually completing after async op...
});
If your doSomeReallyLongAndBoringFunction is really running asynchronously, then it doesn't make sense to run it the way you have setup.
Edit - Here's a simple explanation of the way your code looks to be running vs a refactored version. It's been simplified , so you'll need to fill in the relevant sections with your actual implementation.
var Promise = require('bluebird');
function myAsync() {
setTimeout(function(){
return 'done sleeping';
}, 2000);
};
//The way your code is running
Promise.resolve()
.then(function(){
return 'hello';
})
.then(function(done){
console.log(done);
return myAsync(); //your error is here
})
.then(function(done){
console.log(done);
});
//refactored
Promise.resolve()
.then(function(){
return 'hello';
})
.then(function(done){
console.log(done);
return new Promise(function(resolve) {
setTimeout(function(){
resolve('done sleeping');
}, 2000);
});
})
.then(function(done){
console.log(done);
});
just for clarity, stuff.doSomeReallyLongAndBoringFunction() does not return a Promise itself.
And that's your problem. As it does something asynchronous and you want to get its result, it should return a promise. In fact, that's the case for every asynchronous function, especially then callbacks! It should be something like
module.exports = {
doSomeReallyLongAndBoringFunction: function() {
return db.find()
// ^^^^^^
.then(function() {
return rp(options).then(function(response){
// ^^^^^^
//get some data from remote location
}).then(function(dataFromService) {
//do some jiggery pokery with said data
var marshalledData = dataFromService;
return db.something.create({
// ^^^^^^
Field: 'something'
}).execAsyc();
});
}).catch(function(err) {
});
}
};
Your getSomething method has the same issues, and should look like this:
var createPlace = Promise.promisify(foursquare.createPlaceFromFoursquare);
module.exports = {
getSomething: function(request) {
var p;
if (typeof utils.intTryParse(place) !== 'number')
p = createPlace(sso, place, request); // this returns a promise!
else
p = Promise.resolve({Place: {PlaceId: place}});
return p.then(function(placeObj) {
// ^^^^^^
console.log('Place set as', placeObj);
});
}
};
See also these generic rules for promise development.
doSomeReallyLongAndBoringFunction needs to look like this:
doSomeReallyLongAndBoringFunction: function(param) {
var resolver = Promise.defer();
/*
* do some asynchronous task and when you are finished
* in the callback, do this:
*/
resolver.resolve(resultFromAsyncTask);
/*
*
*
*/
return resolver.promise;
}
So I'm playing around with promises and jQuery and I came up with the code below:
var timeoutAsync = function(millis) {
var deferred = $.Deferred();
setTimeout(function () { deferred.notify(millis); deferred.resolve(); }, millis);
return deferred.promise();
};
$(document).ready(function() {
var firstPromise = timeoutAsync(1000);
var secondPromise = timeoutAsync(2000);
var thirdPromise = timeoutAsync(3000);
var fourthPromise = timeoutAsync(1234);
$.when(firstPromise, secondPromise, thirdPromise, fourthPromise)
.done(function() { alert("LOL"); })
.fail(function() { alert("FAIL"); })
.progress(function(msg) { console.log(msg); });
});
I'd expect my console to show me four values, namely: 1000, 1234, 2000 and 3000.
And it did when I put a console.log statement in the setTimeout callback. But with the code above I get 1000 four times.
I'm pretty sure I'm missing something here but I can't seem to find a hint in the docs as to why this would happen. So why does this happen?
I'm using jQuery 2.1.1 and I'm testing on firefox but it also happens in IE.
It's because you're creating four different deferreds, and passing them all to $.when.
This creates a set of arguments for the progress handler
If you do it like this
.progress(function(msg1, msg2, msg3, msg4) {
console.log(msg1, msg2, msg3, msg4);
});
You'll see the arguments as the deferreds are resolved
FIDDLE
In other words, you're logging only the first argument, which is the value passed to the first deferred, every time.
When checking progress you have to use one deferred object, and then use the notify and progress methods on that deferred, you shouldn't create four different deferreds.
Something like
var timeoutAsync = function(deferred, millis) {
setTimeout(function () {
deferred.notify(millis);
}, millis);
};
$(document).ready(function() {
var deferred = $.Deferred();
var firstPromise = timeoutAsync(deferred, 1000);
var secondPromise = timeoutAsync(deferred, 2000);
var thirdPromise = timeoutAsync(deferred, 3000);
var fourthPromise = timeoutAsync(deferred, 1234);
deferred.done(function() { alert("LOL"); })
.fail(function() { alert("FAIL"); })
.progress(function(msg) {
console.log(msg);
});
});
FIDDLE
jQuery 1.5 brings the new Deferred object and the attached methods .when, .Deferred and ._Deferred.
For those who haven't used .Deferred before, I've annotated the source for it.
What are the possible usages of these new methods, how do we go about fitting them into patterns?
I have already read the API and the source, so I know what it does. My question is how can we use these new features in everyday code?
I have a simple example of a buffer class that calls AJAX requests in order. (Next one starts after the previous one finishes).
/* Class: Buffer
* methods: append
*
* Constructor: takes a function which will be the task handler to be called
*
* .append appends a task to the buffer. Buffer will only call a task when the
* previous task has finished
*/
var Buffer = function(handler) {
var tasks = [];
// empty resolved deferred object
var deferred = $.when();
// handle the next object
function handleNextTask() {
// if the current deferred task has resolved and there are more tasks
if (deferred.isResolved() && tasks.length > 0) {
// grab a task
var task = tasks.shift();
// set the deferred to be deferred returned from the handler
deferred = handler(task);
// if its not a deferred object then set it to be an empty deferred object
if (!(deferred && deferred.promise)) {
deferred = $.when();
}
// if we have tasks left then handle the next one when the current one
// is done.
if (tasks.length > 0) {
deferred.done(handleNextTask);
}
}
}
// appends a task.
this.append = function(task) {
// add to the array
tasks.push(task);
// handle the next task
handleNextTask();
};
};
I'm looking for demonstrations and possible uses of .Deferred and .when.
It would also be lovely to see examples of ._Deferred.
Linking to the new jQuery.ajax source for examples is cheating.
I am particularly interested in what techniques are available when we abstract away whether an operation is synchronously or asynchronously done.
The best use case I can think of is in caching AJAX responses. Here's a modified example from Rebecca Murphey's intro post on the topic:
var cache = {};
function getData( val ){
// return either the cached value or jqXHR object wrapped Promise
return $.when(
cache[ val ] ||
$.ajax('/foo/', {
data: { value: val },
dataType: 'json',
success: function( resp ){
cache[ val ] = resp;
}
})
);
}
getData('foo').then(function(resp){
// do something with the response, which may
// or may not have been retrieved using an
// XHR request.
});
Basically, if the value has already been requested once before it's returned immediately from the cache. Otherwise, an AJAX request fetches the data and adds it to the cache. The $.when/.then doesn't care about any of this; all you need to be concerned about is using the response, which is passed to the .then() handler in both cases. jQuery.when() handles a non-Promise/Deferred as a Completed one, immediately executing any .done() or .then() on the chain.
Deferreds are perfect for when the task may or may not operate asynchronously, and you want to abstract that condition out of the code.
Another real world example using the $.when helper:
$.when($.getJSON('/some/data/'), $.get('template.tpl')).then(function (data, tmpl) {
$(tmpl) // create a jQuery object out of the template
.tmpl(data) // compile it
.appendTo("#target"); // insert it into the DOM
});
Here is a slightly different implementation of an AJAX cache as in ehynd's answer.
As noted in fortuneRice's follow-up question, ehynd's implementation didn't actually prevent multiple identical requests if the requests were performed before one of them had returned. That is,
for (var i=0; i<3; i++) {
getData("xxx");
}
will most likely result in 3 AJAX requests if the result for "xxx" has not already been cached before.
This can be solved by caching the request's Deferreds instead of the result:
var cache = {};
function getData( val ){
// Return a promise from the cache (if available)
// or create a new one (a jqXHR object) and store it in the cache.
var promise = cache[val];
if (!promise) {
promise = $.ajax('/foo/', {
data: { value: val },
dataType: 'json'
});
cache[val] = promise;
}
return promise;
}
$.when(getData('foo')).then(function(resp){
// do something with the response, which may
// or may not have been retreived using an
// XHR request.
});
A deferred can be used in place of a mutex. This is essentially the same as the multiple ajax usage scenarios.
MUTEX
var mutex = 2;
setTimeout(function() {
callback();
}, 800);
setTimeout(function() {
callback();
}, 500);
function callback() {
if (--mutex === 0) {
//run code
}
}
DEFERRED
function timeout(x) {
var dfd = jQuery.Deferred();
setTimeout(function() {
dfd.resolve();
}, x);
return dfd.promise();
}
jQuery.when(
timeout(800), timeout(500)).done(function() {
// run code
});
When using a Deferred as a mutex only, watch out for performance impacts (http://jsperf.com/deferred-vs-mutex/2). Though the convenience, as well as additional benefits supplied by a Deferred is well worth it, and in actual (user driven event based) usage the performance impact should not be noticeable.
This is a self-promotional answer, but I spent a few months researching this and presented the results at jQuery Conference San Francisco 2012.
Here is a free video of the talk:
https://www.youtube.com/watch?v=juRtEEsHI9E
Another use that I've been putting to good purpose is fetching data from multiple sources. In the example below, I'm fetching multiple, independent JSON schema objects used in an existing application for validation between a client and a REST server. In this case, I don't want the browser-side application to start loading data before it has all the schemas loaded. $.when.apply().then() is perfect for this. Thank to Raynos for pointers on using then(fn1, fn2) to monitor for error conditions.
fetch_sources = function (schema_urls) {
var fetch_one = function (url) {
return $.ajax({
url: url,
data: {},
contentType: "application/json; charset=utf-8",
dataType: "json",
});
}
return $.map(schema_urls, fetch_one);
}
var promises = fetch_sources(data['schemas']);
$.when.apply(null, promises).then(
function () {
var schemas = $.map(arguments, function (a) {
return a[0]
});
start_application(schemas);
}, function () {
console.log("FAIL", this, arguments);
});
Another example using Deferreds to implement a cache for any kind of computation (typically some performance-intensive or long-running tasks):
var ResultsCache = function(computationFunction, cacheKeyGenerator) {
this._cache = {};
this._computationFunction = computationFunction;
if (cacheKeyGenerator)
this._cacheKeyGenerator = cacheKeyGenerator;
};
ResultsCache.prototype.compute = function() {
// try to retrieve computation from cache
var cacheKey = this._cacheKeyGenerator.apply(this, arguments);
var promise = this._cache[cacheKey];
// if not yet cached: start computation and store promise in cache
if (!promise) {
var deferred = $.Deferred();
promise = deferred.promise();
this._cache[cacheKey] = promise;
// perform the computation
var args = Array.prototype.slice.call(arguments);
args.push(deferred.resolve);
this._computationFunction.apply(null, args);
}
return promise;
};
// Default cache key generator (works with Booleans, Strings, Numbers and Dates)
// You will need to create your own key generator if you work with Arrays etc.
ResultsCache.prototype._cacheKeyGenerator = function(args) {
return Array.prototype.slice.call(arguments).join("|");
};
Here is an example of using this class to perform some (simulated heavy) calculation:
// The addingMachine will add two numbers
var addingMachine = new ResultsCache(function(a, b, resultHandler) {
console.log("Performing computation: adding " + a + " and " + b);
// simulate rather long calculation time by using a 1s timeout
setTimeout(function() {
var result = a + b;
resultHandler(result);
}, 1000);
});
addingMachine.compute(2, 4).then(function(result) {
console.log("result: " + result);
});
addingMachine.compute(1, 1).then(function(result) {
console.log("result: " + result);
});
// cached result will be used
addingMachine.compute(2, 4).then(function(result) {
console.log("result: " + result);
});
The same underlying cache could be used to cache Ajax requests:
var ajaxCache = new ResultsCache(function(id, resultHandler) {
console.log("Performing Ajax request for id '" + id + "'");
$.getJSON('http://jsfiddle.net/echo/jsonp/?callback=?', {value: id}, function(data) {
resultHandler(data.value);
});
});
ajaxCache.compute("anID").then(function(result) {
console.log("result: " + result);
});
ajaxCache.compute("anotherID").then(function(result) {
console.log("result: " + result);
});
// cached result will be used
ajaxCache.compute("anID").then(function(result) {
console.log("result: " + result);
});
You can play with the above code in this jsFiddle.
1) Use it to ensure an ordered execution of callbacks:
var step1 = new Deferred();
var step2 = new Deferred().done(function() { return step1 });
var step3 = new Deferred().done(function() { return step2 });
step1.done(function() { alert("Step 1") });
step2.done(function() { alert("Step 2") });
step3.done(function() { alert("All done") });
//now the 3 alerts will also be fired in order of 1,2,3
//no matter which Deferred gets resolved first.
step2.resolve();
step3.resolve();
step1.resolve();
2) Use it to verify the status of the app:
var loggedIn = logUserInNow(); //deferred
var databaseReady = openDatabaseNow(); //deferred
jQuery.when(loggedIn, databaseReady).then(function() {
//do something
});
You can use a deferred object to make a fluid design that works well in webkit browsers. Webkit browsers will fire resize event for each pixel the window is resized, unlike FF and IE which fire the event only once for each resize. As a result, you have no control over the order in which the functions bound to your window resize event will execute. Something like this solves the problem:
var resizeQueue = new $.Deferred(); //new is optional but it sure is descriptive
resizeQueue.resolve();
function resizeAlgorithm() {
//some resize code here
}
$(window).resize(function() {
resizeQueue.done(resizeAlgorithm);
});
This will serialize the execution of your code so that it executes as you intended it to. Beware of pitfalls when passing object methods as callbacks to a deferred. Once such method is executed as a callback to deferred, the 'this' reference will be overwritten with reference to the deferred object and will no longer refer to the object the method belongs to.
You can also integrate it with any 3rd-party libraries which makes use of JQuery.
One such library is Backbone, which is actually going to support Deferred in their next version.
I've just used Deferred in real code. In project jQuery Terminal I have function exec that call commands defined by user (like he was entering it and pressing enter), I've added Deferreds to the API and call exec with arrays. like this:
terminal.exec('command').then(function() {
terminal.echo('command finished');
});
or
terminal.exec(['command 1', 'command 2', 'command 3']).then(function() {
terminal.echo('all commands finished');
});
the commands can run async code, and exec need to call user code in order. My first api use pair of pause/resume calls and in new API I call those automatic when user return promise. So user code can just use
return $.get('/some/url');
or
var d = new $.Deferred();
setTimeout(function() {
d.resolve("Hello Deferred"); // resolve value will be echoed
}, 500);
return d.promise();
I use code like this:
exec: function(command, silent, deferred) {
var d;
if ($.isArray(command)) {
return $.when.apply($, $.map(command, function(command) {
return self.exec(command, silent);
}));
}
// both commands executed here (resume will call Term::exec)
if (paused) {
// delay command multiple time
d = deferred || new $.Deferred();
dalyed_commands.push([command, silent, d]);
return d.promise();
} else {
// commands may return promise from user code
// it will resolve exec promise when user promise
// is resolved
var ret = commands(command, silent, true, deferred);
if (!ret) {
if (deferred) {
deferred.resolve(self);
return deferred.promise();
} else {
d = new $.Deferred();
ret = d.promise();
ret.resolve();
}
}
return ret;
}
},
dalyed_commands is used in resume function that call exec again with all dalyed_commands.
and part of the commands function (I've stripped not related parts)
function commands(command, silent, exec, deferred) {
var position = lines.length-1;
// Call user interpreter function
var result = interpreter.interpreter(command, self);
// user code can return a promise
if (result != undefined) {
// new API - auto pause/resume when using promises
self.pause();
return $.when(result).then(function(result) {
// don't echo result if user echo something
if (result && position === lines.length-1) {
display_object(result);
}
// resolve promise from exec. This will fire
// code if used terminal::exec('command').then
if (deferred) {
deferred.resolve();
}
self.resume();
});
}
// this is old API
// if command call pause - wait until resume
if (paused) {
self.bind('resume.command', function() {
// exec with resume/pause in user code
if (deferred) {
deferred.resolve();
}
self.unbind('resume.command');
});
} else {
// this should not happen
if (deferred) {
deferred.resolve();
}
}
}
The answer by ehynds will not work, because it caches the responses data. It should cache the jqXHR which is also a Promise.
Here is the correct code:
var cache = {};
function getData( val ){
// return either the cached value or an
// jqXHR object (which contains a promise)
return cache[ val ] || $.ajax('/foo/', {
data: { value: val },
dataType: 'json',
success: function(data, textStatus, jqXHR){
cache[ val ] = jqXHR;
}
});
}
getData('foo').then(function(resp){
// do something with the response, which may
// or may not have been retreived using an
// XHR request.
});
The answer by Julian D. will work correct and is a better solution.