7

First of all, I must confess, that there are dozens of similar questions here at stackoverflow (this, this and myriads of others), but all of them are nicely solved with the help of a callback function or simply putting a code inside this image.onload event:

image.onload = function () {
    //do other stuff that should be done right after image is loaded
}

But this is not my case. This is a signature of my function that is responsible for loading images:

function patternBuilder (index) {
    var pattern, image, ...
    ...
    image = new Image();
    image.id = "_" + index + "_";
    image.src = "images/_" + index + "_.png";  
    image.onload = function () {
        pattern = ctx.createPattern(image, "repeat");
    }
    return pattern; // !!!! may return undefined if image is not yet loaded
}

So, I need to return! I must do it and I have no other chance. The reason I must follow this signature is that, this pattern is used by some external library function which looks like so:

style: function (feature) {
   var pattern = patternBuilder(feature.get("index"));
   var style = new ol.style.Style({
       fill: new ol.style.Fill({
           color: pattern
       })
   });
   return style;
}

So, even if I can change the logic of my patternBuilder function, I still can not change the external library function. This external function uses patternBuilder and returns a style variable itself. So, there is no room for callbacks.

Community
  • 1
  • 1
Jacobian
  • 10,122
  • 29
  • 128
  • 221
  • You could preload the images before you start your pattern building? – Icepickle Jul 25 '16 at 11:50
  • You may see a parameter `index` passed to the `patternBuilder`. So these images are created dynamically, and I do not know in advance what are all those images. There may be hundreds of them in theory. So, I need to load an image, only when I need it. – Jacobian Jul 25 '16 at 11:53
  • Can you put your code in jsfiddle or codepen, it's so messy to get something from your codes here.. Thanks – ristapk Jul 25 '16 at 11:53
  • This code is a part of a large map application, so unfortunatelly it is impossible to chunk it. That is why I provided the singnature of the external library function. – Jacobian Jul 25 '16 at 11:55
  • Can you generate required images all at once in a [sprite](http://www.w3schools.com/css/css_image_sprites.asp)? load it and generate styles afterwards? – Alena Kastsiukavets Jul 25 '16 at 12:03
  • The catch is, these images may be uploaded by users through the application interface. So, there is in fact no limit of them. – Jacobian Jul 25 '16 at 12:05
  • 2
    So you're saying, that you need to make an async task (like loading an image) sync (immediately return the result). What about the time that it takes to load this image? Basically what you're implying is that you want your whole application/browser-page to freeze, until that image is loaded. really? You have to use an async technique, like the callbacks you already mentioned, and you have to change your functions signature to account for that! ==> http://stackoverflow.com/questions/14220321/how-do-i-return-the-response-from-an-asynchronous-call/14220323#14220323 – Thomas Jul 25 '16 at 12:06
  • @Thomas. I wish, I could. But the problem is, the external library function uses this nasty `return` statement =) – Jacobian Jul 25 '16 at 12:07
  • well, if you didn't remove anything significant where the `...` are, then this function is just broken. In a sense of that this task simply can not work that way. *Edit:* It **may** work with data-urls, but even there it's not guaranteed, that loading this data-url into an image-node is sync through all browser. – Thomas Jul 25 '16 at 12:09
  • Indeed, it is broked and I stress it in the comment that it may return undefined if image is not yet loaded. But this is what I absolutely need. – Jacobian Jul 25 '16 at 12:13
  • right above `image.onload` it creates an image from url, like you said. – Jacobian Jul 25 '16 at 12:14
  • 1
    As I already posted: you have to change that to some async implementation! If you say it's an external lib, and you can't fix that function, then you need a different lib, one that is not broken in one of it's core functions. again, read this: [How do I return the response from an asynchronous call](http://stackoverflow.com/questions/14220321/how-do-i-return-the-response-from-an-asynchronous-call/14220323#14220323) – Thomas Jul 25 '16 at 12:16
  • Which library is this using, what is the name of the external library, if we would know what the pattern might need, it could provide a "help" on how to construct it in a synchronous way – Icepickle Jul 25 '16 at 12:46

2 Answers2

1

Here how this could work with promises

a simple wrapper to return image promises:

//takes an url and returns a promise of a (loaded) Image.
function getImage(url){
    return new Promise(function(){
        var image = new Image();
        image.onload = function(){ resolve(image); };
        image.onerror = reject; //TODO: resolve that to sth. better
        image.src = url;
    });
}

and your patternBuilder

function patternBuilder (index) {
    //returns now a pattern-promise
    return getImage( "images/_" + index + "_.png" ).then(function(image){
        //image.id = "_" + index + "_"; //what exactly is this ID for?
        return ctx.createPattern(image, "repeat");
    });
}

//you may want to add some caching
var patternCache = Object.create(null);
function patternBuilder(index) {
    if(!patternCache[index]){
        patternCache[index] = getImage( "images/_" + index + "_.png" ).then(image => ctx.createPattern(image, "repeat"));
    }
    //still returns pattern-promise, but some may have been resolved a "long time" ago.
    return patternCache[index];
}

and the function you use the pattern:

style: function(feature) {
    //returns a style-promise
    return patternBuilder(feature.get("index")).then(function(pattern){
        return new ol.style.Style({
            fill: new ol.style.Fill({
                color: pattern
            })
        })
    })
}
Thomas
  • 11,958
  • 1
  • 14
  • 23
  • That looks great. I will try it. – Jacobian Jul 25 '16 at 13:09
  • I tried it. I'm not that good at JavaScript Promises, so I do not know what is wrong with that and how to cope with that, but now `style` function returns a `Promise { : "pending" }` object, instead of a style object. And that results in other errors. – Jacobian Jul 25 '16 at 13:39
  • @Jacobian True, now you have to update the items that use the "style" function to handle it as a promise and also use the "then" keyword – Icepickle Jul 25 '16 at 13:48
0

Have you tried using a promise? You can control de callback so when the image has been load you can return the function.

dbmdlr
  • 21
  • 5