Today I've seen a strange problem when using RxJS. Please help me inspect it.
The problem I am working on is: "Given an array of image URLs, load and append all images to a div."
All the code snippets to demonstrate is here:
At the beginning, I used the first snippet
.
However, Rx.Observable.prototype.flatMap
sometimes puts the images in wrong order (this behavior is noticed in the documentation). So, I changed to use concatMap
(second snippet
).
This time, only the first image is loaded. I took some time inspect the problem. I doubt that event load
is not trigged from image
. But the most confusing situation is that when I add some code to listen to image
's load
event only, it showed me that the event is trigged properly... (third snippet
).
Then I decided to write another version using $.Deferred
(fourth snippet
).
It worked...
Could you please tell me what is the problem? Thank you very much!
Because fromEvent(image, 'load')
on the first sub-observable is not completed, other sub-observables are waiting forever. So you should complete sub-observable after first event.
Use take(1)
.
excerpt from your second snippet
...
var loadedImageStream = Rx.Observable
.fromEvent(image, 'load')
.map(function() {
return image;
})
...
Add take(1) to complete sub-observable
...
var loadedImageStream = Rx.Observable
.fromEvent(image, 'load')
.map(function() {
return image;
})
.take(1)
...
EDIT:
Using concatMap
makes loading image sequential, so it is slow.
If you pass index
, you can use replace
instead of append
to keep the order. In this case, you can use flatMap
, which enables fast concurrent loading.
$(function () {
var imageURLList = [
'https://placehold.it/500x100',
'https://placehold.it/500x200',
'https://placehold.it/500x300',
'https://placehold.it/500x400',
'https://placehold.it/500x500'
];
var imagesDOM = $('#images');
Rx.Observable
.fromArray(imageURLList)
.do(function (imageURL) {
imagesDOM.append(new Image()); // placeholder
})
.flatMap(function (imageURL, index) {
var image = new Image();
var loadedImageStream = Rx.Observable
.fromEvent(image, 'load')
.map(function () {
return [image, index];
})
.take(1)
image.src = imageURL;
return loadedImageStream;
})
.subscribe(function (image_index) {
var image = image_index[0];
var index = image_index[1];
imagesDOM.children().get(index).replaceWith(image);
})
})