How to load images async with RxJs and perform a method when all loaded

前端 未结 6 882
臣服心动
臣服心动 2020-12-30 10:25

I am trying to convert my promise based code to RxJs but have a hard time to get my head around Rx especially RxJs.

I have a an array with paths.

var         


        
相关标签:
6条回答
  • 2020-12-30 10:28

    I think you don't have 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 === paths.length)
    ).subscribe((images) => {
       // do what you want with images
    });
    
    0 讨论(0)
  • 2020-12-30 10:33

    I don't think you can do that easily with observables, as there's nothing there to indicate a finish (unless you have an initial size). Look at the other answers for the Rx version.

    However, you can use an array of Promises:

    /**
     * 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 that, you can easily do something like this:

    var imageUrls = ['url1', 'url2', 'url3'];
    Promise.all(imageUrls.map(loadImageAsync))
        .then(function(arrayOfImageElements) {
            // All done!
        });
    
    0 讨论(0)
  • 2020-12-30 10:35
    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 short:

    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
    });
    
    0 讨论(0)
  • 2020-12-30 10:39

    At first you need a function that will create a Observable or Promise for separate 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 load 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
      })
    
    0 讨论(0)
  • 2020-12-30 10:48

    The other RX based solutions here did not really work for me. Bogdan Savluk’s version did not work at all. Benjamin Gruenbaum’s version waits until an image is loaded before starting to load the next image so it gets really slow (correct me if I am wrong) Here is my solution which just compares the total amount of images with the number of already loaded images and if they are equal, the onNext() method of the returned Observable gets called with the 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)
          }
    
        })
    
      })
    
    }
    

    Usage:

    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)
      })
    
    0 讨论(0)
  • 2020-12-30 10:53

    Here is the Angular / Typescript version to load an Image with 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 讨论(0)
提交回复
热议问题