Puzzling over returning a value from a javascript callback in a tricky situation

I'm making a small Node.js app for personal use that sideloads pics uploaded to my twitter account (pic.twitter.com links) to imgur using their anonymous API, replaces the pic.twitter.com links with the newly-obtained imgur links and saves them tweets to disk. Here's the bit of code that does this:

var 
    jsdom   = require( 'jsdom' )
,   request = require( 'request' )
,   t_pic   = /http:\/\/pic.twitter.com\/([a-z0-9]+)/gi;

function rehostImage( match ) {
    request( match, function( e, r, b ) {
        if ( e ) throw e;
        jsdom.env(
            {
                html: b,
                scripts: ['http://code.jquery.com/jquery-1.7.2.min.js']
            }, 
            function( err, window ) {
                var $ = window.jQuery;
                request.post( 
                    'http://api.imgur.com/2/upload.json', 
                    { 
                        key: 'gh10z9DFZ009876E342dfsfd34234dszSD65XRV5',
                        image:$( '.twimg' ).find( 'img' ).attr( 'src' ) 
                    }, 
                    function( data ) { 
                        return data.upload.links.original; 
                    });
            });
    });
}

var tweets = [], i = 0;

/* code to GET twitter page using "request" goes here similar to rehostImage */

/* same jsdom boilerplate as in rehostImage function goes here to get 
   the jQuery handle on the page DOM. Once we get "$"... */

$( '.tweet-text' ).each( function() { 
    i++;
    tweets[ i ] = $( this ).html();
    if( tweets[i].match( t_pic ) ) { tweets[i].replace( t_pic, rehostImage ); }
});

What the code tries to do is simple:

  1. GETs my twitter page
  2. Parses each tweet for any pic.twitter.com links
  3. GETs that page, traverses the DOM, finds the image URL and uploads to imgur via their anon API

The problem is the regex replace here tweets[i].replace( t_pic, rehostImage ). replace takes a function as a second param whose return value is used to replace the matched string; in this case the final imgur URL after upload, data.upload.links.original, replaces the pic.twitter.com URL in my tweet which I then save to my local disk.

How do I get rehostImage to return data.upload.links.original back to the regex replace, when it's all happening in async through callbacks? The usual approach is to take the final operation, the regex replace, and pass it as callback that can be run after data.upload.links.original but since the regex replace happens inline I don't see how I can apply that approach here.

Any pointers?

If you want to defer execution of your replace until the async calls return, you could implement this using $.deferred.

This is an example of how you would use it, you'd probably need to tweak it to suit your specific behavior.

First, you need a deferred object:

var deferred = $.Deferred();

Then your async function needs to return a promise:

function rehostImage( match ) {
    // snip
    return deferred.promise();
}

Your replace callback would wait on the deferred to resolve:

tweets[i].replace(t_pic, function () {
    rehostImage().then(function (data) {
        // the callback you want to execute when the deferred resolves
    });
});

And finally your async function needs to resolve the deferred:

function( data ) {
    deferred.resolve(data.upload.links.original); 
});