How to load async images using RxJs and execute a method when all downloaded

I'm trying to convert my promise-based code to RxJs, but it's hard for me to find my head around Rx, especially RxJs.

I have an array with paths.

var paths = ["imagePath1","imagePath2"]; 

And I like to upload images in Javascript

 var img = new Image(); img.src = imagePath; image.onload // <- when this callback fires I'll add them to the images array 

and when all the images are uploaded, I like to execute the method.

I know that there is

 Rx.Observable.fromArray(imagepathes) 

there is something like

 Rx.Observable.fromCallback(...) 

and there is something like flatMapLatest(...) And Rx.Observable.interval or a time-based scheduler

Based on my research, I would suggest that these will be the ingredients to solve it, but I cannot get the composition to work.

So, how do I load images from array paths and when all the images are loaded do I execute the interval based method?

Thanks for any help.

+6
source share
6 answers

First you need a function that will create an Observable or Promise for a single image:

 function loadImage(imagePath){ return Rx.Observable.create(function(observer){ var img = new Image(); img.src = imagePath; img.onload = function(){ observer.onNext(img); observer.onCompleted(); } img.onError = function(err){ observer.onError(err); } }); } 

Than you can use it to download all images

 Rx.Observable .fromArray(imagepathes) .concatMap(loadImage) // or flatMap to get images in load order .toArray() .subscribe(function(images){ // do something with loaded images }) 
+8
source

I do not think that you can do this easily with observables, since there is nothing there to indicate the finish (if you do not have an initial size). Look at the other answers for the Rx version.

However, you can use the Promises array:

 /** * Loads an image and returns a promise * @param {string} url - URL of image to load * @return {Promise<Image>} - Promise for an image once finished loading. */ function loadImageAsync(url) { return new Promise(function(resolve, reject) { var img = new Image(); img.src = imagePath; image.onload = function() { resolve(img); }; image.onerror = reject; }); } 

And with this you can easily do something like this:

 var imageUrls = ['url1', 'url2', 'url3']; Promise.all(imageUrls.map(loadImageAsync)) .then(function(arrayOfImageElements) { // All done! }); 
+1
source
 function loadImage(url){ var img = new Image; img.src = url; var o = new Rx.Subject(); img.onload = function(){ o.onNext(img); o.onCompleted(); }; img.onerror = function(e){ o.onError(e); }; // no fromEvent for err handling return o; } var imageUrls = ['url1', 'url2', 'url3']; var joined = Rx.Observable.merge(imageUrls.map(loadImage)); // consume one by one: joined.subscribe(function(item){ // wait for item }); joined.toArray().subscribe(function(arr){ // access results array in arr }); 

Or in a word:

 var imageUrls = ['url1', 'url2', 'url3']; fromArray(imageUrls).map(url => { var img = new Image; img.src = url; return fromEvent(img, "load"); }).toArray().subscribe(function(arr){ // access results here }); 
+1
source

Other RX-based solutions here didn’t really work for me. The version of Bogdan Savluks did not work at all. The Benjamin Gruenbaums version waits for the image to load before loading the next image so that it becomes very slow (correct me if I am wrong) Here is my solution that simply compares the total number of images with the number of images already loaded, and if they are equal, the method The onNext () of the returned Observable is called with an array of images as an argument:

 var imagesLoaded = function (sources) { return Rx.Observable.create(function (observer) { var numImages = sources.length var loaded = 0 var images = [] function onComplete (img) { images.push(img) console.log('loaded: ', img) loaded += 1 if (loaded === numImages) { observer.onNext(images) observer.onCompleted() } } sources.forEach(function (src) { var img = new Image() img.onload = function () { onComplete(img) } console.log('add src: ' + src) img.src = src if (img.complete) { img.onload = null onComplete(img) } }) }) } 

Using:

 console.time('load images'); // start measuring execution time imagesLoaded(sources) // use flatMap to get the individual images // .flatMap(function (x) { // return Rx.Observable.from(x) // }) .subscribe(function (x) { console.timeEnd('load images'); // see how fast this was console.log(x) }) 
+1
source

I think you do not need to create an Observable yourself for this.

 import { from, fromEvent } from 'rxjs'; import { mergeMap, map, scan, filter } from 'rxjs/operators'; const paths = ["imagePath1","imagePath2"]; from(paths).pipe( mergeMap((path) => { const img = new Image(); img.src = path; return fromEvent(img, 'load').pipe( map((e) => e.target) ); }), scan((acc, curr) => [...acc, curr], []), filter((images) => images.length === path.length) ).subscribe((images) => { // do what you want with images }); 
0
source

Here is the version of Angular / Typescript for loading an image using RxJS:

 import { Observable, Observer } from "rxjs"; public loadImage(imagePath: string): Observable<HTMLImageElement> { return Observable.create((observer: Observer<HTMLImageElement>) => { var img = new Image(); img.src = imagePath; img.onload = () => { observer.next(img); observer.complete(); }; img.onerror = err => { observer.error(err); }; }); } 
0
source

All Articles