Wrapping the code in a $.Deferred (or native promise) won't help even if you do manage to get the promise back to the calling code before doing the long-running work (for instance, via setTimeout). All it would accomplish is making the main UI thread seize up later, soon after longrunning returned the promise, instead of when calling longrunning itself. So, not useful. :-)
If the function in question doesn't manipulate the DOM, or if the manipulations it does can be separated from the long-running logic, this is a good candidate to be moved to a web worker (specification, MDN), so it doesn't get run on the main UI thread at all, but instead gets run in a parallel worker thread, leaving the UI free to continue to be responsive.
longrunning wouldn't do the actual work, it would just postMessage the worker to ask it to do the work, and then resolve your promise when it gets back a message that the work is done. Something along these lines (this is just a code sketch, not a fully-implemented solution):
var pendingWork = {};
var workId = 0;
var worker = new Worker("path/to/web/worker.js");
worker.addEventListener("message", function(e) {
// Worker has finished some work, resolve the Deferred
var d = pendingWork[e.data.id];
if (!d) {
console.error("Got result for work ID " + e.data.id + " but no pending entry for it was found.");
} else {
if (e.data.success) {
d.resolve(e.data.result);
} else {
d.reject(e.data.error);
}
delete pendingWork[e.data.id];
}
});
function longrunning(info) {
// Get an identifier for this work
var id = ++workid;
var d = pendingWork[id] = $.Deferred();
worker.postMessage({id: id, info: info});
return d.promise();
}
(That assumes what the worker sends back is an object with the properties id [the work ID], success [flag], and either result [the result] or error [the error].)
As you can see, there we have longrunning send the work to the worker and return a promise for it; when the worker sends the work back, a listener resolves the Deferred.
If the long-running task does need to do DOM manipulation as part of its work, it could post the necessary information back to the main script to have it do those manipulations on its behalf as necessary. The viability of that naturally depends on what the code is doing.
Naturally, you could use native promises rather than jQuery's $.Deferred, if you only have to run on up-to-date browsers (or include a polyfill):
var pendingWork = {};
var workId = 0;
var worker = new Worker("path/to/web/worker.js");
worker.addEventListener("message", function(e) {
// Worker has finished some work, resolve the Deferred
var work = pendingWork[e.data.id];
if (!work) {
console.error("Got result for work ID " + e.data.id + " but no pending entry for it was found.");
} else {
if (e.data.success) {
work.resolve(e.data.result);
} else {
work.reject(e.data.error);
}
delete pendingWork[e.data.id];
}
});
function longrunning(info) {
return new Promise(function(resolve, reject) {
// Get an identifier for this work
var id = ++workid;
pendingWork[id] = {resolve: resolve, reject: reject};
worker.postMessage({id: id, info: info});
});
}