等待代码直到图像大小调整完成 RXJS

Wait code until image resize finishes RXJS

我正在努力理解并使这段代码正常工作。如您所见,它是一个可观察到的 Rxjs 服务。

我使用 setTimeout 做了一个解决方法,尝试等到调整大小完成。否则,它会产生一个空的 fileBlobArray。

我研究并尝试了一些方法,例如 promise.all 和 async-await 但没有成功。

感谢您的帮助。

import { Injectable } from '@angular/core';
import { Observable, Subject } from 'rxjs';

@Injectable({
  providedIn: 'root'
})
export class resizeService {

  constructor() { }

  resizeImage(files: FileList, maxWidth: number, maxHeight: number): Observable<any> {
    let resizedFileSubject = new Subject();

    let fileBlobArray: Blob[] = [];

    Array.from(files).map((file, i, { length }) => {
      let image: HTMLImageElement = new Image();
      image.src = URL.createObjectURL(file);

      image.onload = async () => {
        let width = image.width;
        let height = image.height;
        let canvas = document.createElement('canvas');
        let ctx: any = canvas.getContext("2d");
        let newHeight;
        let newWidth;
        const ratio = width / height;

        // Calculate aspect ratio
        if (width > height) {
          newWidth = maxHeight * ratio;
          newHeight = maxHeight;
        } else {
          newWidth = maxWidth * ratio;
          newHeight = maxWidth;
        }

        canvas.width = newWidth;
        canvas.height = newHeight;

        // Draw image on canvas
        ctx.drawImage(image, 0, 0, newWidth, newHeight);
        fileBlobArray.push(await this.b64ToBlob(canvas.toDataURL("image/jpeg")));

        // Detect end of loop
        if (i + 1 === length) {
          // Wait to get async data - Workaround :(
          // setTimeout(() => {
            resizedFileSubject.next(fileBlobArray);
            // console.log('next: ', fileBlobArray)
          // }, 1000);
        }
      }
    });

    return resizedFileSubject.asObservable();
  }

  /**
  * Convert BASE64 to BLOB
  * @param base64Image Pass Base64 image data to convert into the BLOB
  */
  private async b64ToBlob(base64Image: string) {
    const parts = base64Image.split(';base64,');
    const imageType = parts[0].split(':')[1];
    const decodedData = window.atob(parts[1]);
    const uInt8Array = new Uint8Array(decodedData.length);

    for (let i = 0; i < decodedData.length; ++i) {
      uInt8Array[i] = decodedData.charCodeAt(i);
    }

    return new Blob([uInt8Array], { type: imageType });
  }
}

我建议按此顺序研究异步 JS/TS 的基础知识。

  1. 创建并return承诺。
  2. Promise.all()
  3. 异步/等待
  4. RxJS

考虑到您的示例代码,您实际上并不需要 RxJS 来满足此要求。您的服务方法的目的是 return 基于参数中传递的图像数组的 blob 数组。

在理解上述承诺的基础知识之前,请避免在代码中随意使用 async/await。目前您的所有 async/await 关键字都没有做任何事情,因为您的代码中没有任何承诺。

纯粹从需求来看,当 onload 事件在单个文件上触发时,您希望 return 一个新的 blob。为此,您需要创建一个在调用 onload 函数时解析的承诺。为简单起见,我将您的 onload 函数移至名为 handleOnLoad().

的单独服务方法
return new Promise((res, rej) => {
    let image: HTMLImageElement = new Image();
    image.src = URL.createObjectURL(file);

    image.onload = () => res(this.handleOnLoad(image, maxWidth, maxHeight));
});

现在您的 Array.from(files).map() 可以 return 这些承诺的数组。数组中的每个文件一个。您现在可以将此数组包装在 Promise.all() 中并将其作为您的 return 对象。

这是一个完整的例子。我添加了一个 try/catch 块来处理承诺拒绝:

import { Injectable } from '@angular/core';

@Injectable({
  providedIn: 'root',
})
export class resizeService {
  constructor() {}

  resizeImage(files: FileList, maxWidth: number, maxHeight: number) {
    return Promise.all(
      Array.from(files).map(file => {
        return new Promise((res, rej) => {
          try {
            let image: HTMLImageElement = new Image();
            image.src = URL.createObjectURL(file);

            image.onload = () => res(this.handleOnLoad(image, maxWidth, maxHeight));
          } catch (e) {
            rej(e);
          }
        });
      })
    );
  }

  private handleOnLoad(
    image: HTMLImageElement,
    maxWidth: number,
    maxHeight: number
  ) {
    let width = image.width;
    let height = image.height;
    let canvas = document.createElement('canvas');
    let ctx = canvas.getContext('2d');
    let newHeight;
    let newWidth;
    const ratio = width / height;

    // Calculate aspect ratio
    if (width > height) {
      newWidth = maxHeight * ratio;
      newHeight = maxHeight;
    } else {
      newWidth = maxWidth * ratio;
      newHeight = maxWidth;
    }

    canvas.width = newWidth;
    canvas.height = newHeight;

    // Draw image on canvas
    ctx.drawImage(image, 0, 0, newWidth, newHeight);
    return this.b64ToBlob(canvas.toDataURL('image/jpeg'));
  }

  /**
   * Convert BASE64 to BLOB
   * @param base64Image Pass Base64 image data to convert into the BLOB
   */
  private b64ToBlob(base64Image: string) {
    const parts = base64Image.split(';base64,');
    const imageType = parts[0].split(':')[1];
    const decodedData = window.atob(parts[1]);
    const uInt8Array = new Uint8Array(decodedData.length);

    for (let i = 0; i < decodedData.length; ++i) {
      uInt8Array[i] = decodedData.charCodeAt(i);
    }

    return new Blob([uInt8Array], { type: imageType });
  }
}

现在 resizeImage() 正在 return 承诺。您可以在 async/await 函数中调用它。

/** Component Class **/
public async resizeEvent(){
  const newImages = await service.resizeImage(files, 1920, 1080);
}