9

我正在尝试将基于 Promise 的代码转换为 RxJs,但很难理解 Rx,尤其是 RxJs。

我有一个带有路径的数组。

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

我喜欢用 Javascript 加载图像

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

当所有图像都加载完毕后,我喜欢执行一个方法。

我知道有

Rx.Observable.fromArray(imagepathes)

还有类似的东西

Rx.Observable.fromCallback(...)

并且有类似flatMapLatest(...) AndRx.Observable.interval或基于时间的调度程序

根据我的研究,我认为这些将是解决问题的要素,但我无法让组合发挥作用。

那么如何从数组路径加载图像以及加载所有图像时我基于间隔执行一个方法?

谢谢你的帮助。

4

7 回答 7

12

首先,您需要一个为单独的图像创建 Observable 或 Promise 的函数:

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);
     }
   });
}

比你可以用它来加载所有图像

Rx.Observable
  .fromArray(imagepathes)
  .concatMap(loadImage) // or flatMap to get images in load order
  .toArray()
  .subscribe(function(images){
    // do something with loaded images
  })
于 2015-07-12T11:08:14.840 回答
1

我不认为你可以用 observables 轻松做到这一点,因为那里没有任何东西表明完成(除非你有一个初始大小)。查看 Rx 版本的其他答案。

但是,您可以使用 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;
    });
}

有了它,您可以轻松地执行以下操作:

var imageUrls = ['url1', 'url2', 'url3'];
Promise.all(imageUrls.map(loadImageAsync))
    .then(function(arrayOfImageElements) {
        // All done!
    });
于 2015-07-12T10:41:36.653 回答
1
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
});

或者简而言之:

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
});
于 2015-07-12T11:08:12.707 回答
1

这里的其他基于 RX 的解决方案并不适合我。Bogdan Savluk 的版本根本不起作用。Benjamin Gruenbaum 的版本在开始加载下一个图像之前等到加载图像,所以它变得非常慢(如果我错了,请纠正我)这是我的解决方案,它只是将图像总量与已加载图像的数量进行比较,如果它们是相等的,返回的 Observable 的 onNext() 方法以图像数组作为参数被调用:

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)
      }

    })

  })

}

用法:

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)
  })
于 2015-08-16T12:27:37.927 回答
1

这是使用 RxJS 加载图像的 Angular / Typescript 版本:

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);
    };
  });
}
于 2018-12-14T15:48:39.703 回答
0

我认为您不必为此创建Observable自己。

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
});
于 2018-08-06T01:56:10.000 回答
0

这是一个更好的实现,如果您取消订阅 Observable https://stackblitz.com/edit/rxjs-loadimage?file=index.ts ,它会取消加载图像

import { Observable, Subscriber } from "rxjs";

/**
 * RxJS Observable of loading image that is cancelable
 */
function loadImage(
  url: string,
  crossOrigin?: string
): Observable<HTMLImageElement> {
  return new Observable(function subscriber(subscriber) {
    let img = new Image();
    img.onload = function onload() {
      subscriber.next(img);
      subscriber.complete();
    };
    img.onerror = function onerror(err: Event | string) {
      subscriber.error(err);
    };
    // data-urls appear to be buggy with crossOrigin
    // https://github.com/kangax/fabric.js/commit/d0abb90f1cd5c5ef9d2a94d3fb21a22330da3e0a#commitcomment-4513767
    // see https://code.google.com/p/chromium/issues/detail?id=315152
    //     https://bugzilla.mozilla.org/show_bug.cgi?id=935069
    // crossOrigin null is the same as not set.
    if (
      url.indexOf("data") !== 0 &&
      crossOrigin !== undefined &&
      crossOrigin !== null
    ) {
      img.crossOrigin = crossOrigin;
    }
    // IE10 / IE11-Fix: SVG contents from data: URI
    // will only be available if the IMG is present
    // in the DOM (and visible)
    if (url.substring(0, 14) === "data:image/svg") {
      // TODO: Implement this :)
      // img.onload = null;
      // fabric.util.loadImageInDom(img, onLoadCallback);
    }
    img.src = url;
    return function unsubscribe() {
      img.onload = img.onerror = undefined;
      if (!img.complete) {
        img.src = "";
      }
      img = undefined;
    };
  });
}

// Example
const cacheBurst = new Date().getTime();
const imgUrl = `https://i.pinimg.com/originals/36/0c/62/360c628d043b2461d011d0b7f9b4d880.jpg?nocache=${cacheBurst}`;

const s = loadImage(imgUrl).subscribe(
  img => {
    console.log("Img", img);
  },
  err => {
    console.log("Err", err);
  }
);

setTimeout(() => {
  // uncomment to check how canceling works
  // s.unsubscribe();
}, 100);

于 2021-01-20T18:05:37.007 回答