0

I have little trouble with asynchronous functions. The asynchronous function in getImageSizeByUrl() could be handled by using $.Deferred().

function getImageSizeByUrl(url) {
    var deferredReady = $.Deferred();

    var tmpImg = new Image();
    tmpImg.src = url;
    // asynchronous function
    $(tmpImg).on('load',function(){
        getImageSizeByUrlWidth = tmpImg.width;
        getImageSizeByUrlHeight = tmpImg.height;

        deferredReady.resolve();
    });

    return deferredReady.promise();
}

But now I have two other problems. In the lower function, the function $.when itself is asynchronous. So it only wraps the problem. So the function chrome.webRequest.onBeforeRequest.addListener() is done, without waiting for the return of getImageSizeByUrl(). How in this tricky case it is possible to get a synchronous behaviour?

My second problem is the nested return value in the lower function. return {redirectUrl: "https://www.google.de/images/srpr/logo11w.png"}; and return {} should be the return for the parent function function(info).

(Background: The lower listener function of a chrome extension checks every image before loading. The listener needs a return value for redirecting or allowing the image loading. But the listener function doesn't wait for the result of the upper function getImageSizeByUrl and returns an empty value.)

// listener for image loading
chrome.webRequest.onBeforeRequest.addListener(
    function(info) {
        console.log("test1");
        $.when(
            getImageSizeByUrl(info.url)
        ).done( 
            function() {
                // for example: if image has to big height or width, load alternative image
                if(getImageSizeByUrlHeight > 200 || getImageSizeByUrlWidth > 200) {
                    // load alternative image
                    console.log("test3");
                    return {redirectUrl: "https://www.google.de/images/srpr/logo11w.png"};
                } else {
                    // image is not big. allow loading
                    console.log("test3");
                    return {};
                }
            }
        );
        console.log("test3");
    },
    {
        urls: [
            "<all_urls>",
        ],
        types: ["image"]
    },
    ["blocking"]
);

Hope someone is able to help me/give me some hints. :)
Best regards

  • Wow, we're up to almost 20 times a day now that people ask how to return a value synchronously from an async operation. It simply cannot be done and writing code that expects that is just wasted code that needs to be restructured to work with an async operation. Please go read this http://stackoverflow.com/questions/14220321/how-to-return-the-response-from-an-ajax-call to understand your options (hint the answer in your code will lie in the correct use of promises to get async return values out to some other code and since promises ultimately use callbacks, that's what you'll be using. – jfriend00 Nov 21 '14 at 23:57
  • If it's the `onBeforeRequest` handler that has to return the value that you can only get via an async operation, then you simply can't make the code work this way. You will need a new design that does not rely on returning an async-retrieved value. You will either need to fetch those values ahead of time (so they are already available) or not use onBeforeRequest and solve your overall problem some other way. – jfriend00 Nov 21 '14 at 23:59
  • FYI, you don't need `$.when()` at all. You can just do `getImageSizeByUrl(info.url).done(...);` `$.when()` is only needed when you want to be notified when multiple promises are all done. – jfriend00 Nov 22 '14 at 00:36
  • I might also mention that your `getImageSizeByUrl()` has a potential problem in it. You need to set the `load` event handler BEFORE you set the `.src` property. This is because some browsers (like some versions of IE) will fire the `load` event immediately when you set the `.src` property if the image is in the browser cache. If that happens with your code, you will miss the `load` event. So, just move the setting of `.src` to after you install the `load` event handler. – jfriend00 Nov 22 '14 at 01:51
  • Thanks for your answers. I read another question with almost the same problem. http://stackoverflow.com/questions/9121902/call-an-asynchronous-javascript-function-synchronously It seems that there is no way to solve this problem without changing the code layout as you said. – YellowBlue Nov 22 '14 at 10:59
  • But which option exists, if a synchronous function needs for their return value the return of an asynchronous function? Its not possible to run the asynchronous function before the synchronous function, because the synchronous function generates the input (url of an image) for the asynchronous function. Can't imagine, that there is noch solution for this kind of problem. :( – YellowBlue Nov 22 '14 at 10:59
  • You simply can't do it this way. There is NO solution that returns an async value from a synchronous function. Javascript simply doesn't work that way. You will have to work downstream in your problem and make the ultimate consumer of the image size work off a callback or a promise so it can execute when the data is ready. I'm sure the larger problem is solvable, just not the way you have your code structured. – jfriend00 Nov 22 '14 at 15:04
  • Also, stuffing the image size into two globals and expecting that to be a robust way to pass the data is a major problem too - particularly with async operations. You could easily just put the data into the promise by making it the resolved value of the promise. Then, the consumer of the promise can get the data from their `.then()` handler. – jfriend00 Nov 22 '14 at 15:05

1 Answers1

0

You might find it more useful to write a constructor that returns a "promisified Image", ie an instance of window.Image with a custom .setSrc() method that returns a promise.

Something like this (untested) should do it :

function PromisifiedImg() {
    var img = new Image();
    img.setSrc = function(src, limits) {
        var dfrd = $.Deferred();
        img.onload = function() {
            if(limits && (img.width > limits.width || img.height > limits.height)) {
                var error = new Error('img dimension(s) exceed limits');
                error.number = 101; // 101 is arbitrary
                dfrd.reject(error);
            } else {
                dfrd.resolve(img);
            }
        };
        img.onerror = function(e) {
            e.number = e.number || 0;
            dfrd.reject(e);
        };
        img.src = src;
        return dfrd.promise();
    };
    return img;
}

Now you can call the returned Image's .setSrc(...) method instead of .src = '...'

Your ....addListener() handler might look something like this :

chrome.webRequest.onBeforeRequest.addListener(
    function(info) {
        var img = new PromisifiedImg();
        img.setSrc(info.url, { width:200, height:200 }).then(null, function(e) {
            if(e.number === 101) { // detect the custom error condition
                console.log(e.message);
                return img.setSrc("https://www.google.de/images/srpr/logo11w.png");
            }
        });
    },
    { urls: [ "<all_urls>" ], types: ["image"] },
    ["blocking"]
);

An advantage of doing it this way is that the returned Image is reusable. A second call to .setSrc() would operate on the same instance of Image, but would return a new promise.

Note: I've not tried to penetrate the chrome.webRequest.onBeforeRequest.addListener() wrapper the other parameters passed to it. After a casual glance at the documentation, this looks like it might be the wrong approach. Therefore this answer may well offer only a partial solution.

Roamer-1888
  • 19,138
  • 5
  • 33
  • 44
  • But, this doesn't solve the OP's issue that they need to return a value from the listener callback. – jfriend00 Nov 22 '14 at 15:00
  • @jFriend00, Are you sure? We have clearly read the question differently. The OP's statement "the listener needs a return value" [from getImageSizeByUrl()] is not the same as "need to return a value from the listener callback". – Roamer-1888 Nov 22 '14 at 15:12
  • That's been the whole conversation so far. They want to return some redirect info from the synchronous listener callback function and that redirect info is based on the async image size. – jfriend00 Nov 22 '14 at 15:15
  • OK but I'm left struggling with the concept of returning something from a listener. Return to where? – Roamer-1888 Nov 22 '14 at 15:20
  • Return to the caller of the listener callback presumably. It's not that unheard of that a callback returns a value. As an example, jQuery lets you return `false` from a listener to stop event propagation. Promise callbacks accept return values (other promises or values). – jfriend00 Nov 22 '14 at 15:28
  • I truly believe the OP needs to purge any notion of returning anything from the listener and start concentrating on what is returned from `getImageSizeByUrl()` (or the like) to the listener. – Roamer-1888 Nov 22 '14 at 15:34
  • FYI, the doc for `chrome.webRequest.onBeforeRequest.addListener` is [here](https://developer.chrome.com/extensions/webRequest#event-onBeforeRequest). – jfriend00 Nov 22 '14 at 15:46
  • @jfriend00, I have added a footnote to my answer. – Roamer-1888 Nov 22 '14 at 18:00
  • Thanks for the helpful und great answers. I restructered the code. Now then the DOM is ready, the code checks the DOM for image links and checks the size with the upper function getImageSizeByUrl. In some cases, this is before the chrome.webRequest.onBeforeRequest.addListener firers. In the other cases I use a dirty ajax call (async: false) for calling a external PHP api, telling me the image size. I know, this i like killing kittens. – YellowBlue Nov 25 '14 at 20:04
  • Aha, hybrid approach. Sounds a lot better. – Roamer-1888 Nov 25 '14 at 22:02