等待代码直到图像大小调整完成 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 的基础知识。
- 创建并return承诺。
- Promise.all()
- 异步/等待
- 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);
}
我正在努力理解并使这段代码正常工作。如您所见,它是一个可观察到的 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 的基础知识。
- 创建并return承诺。
- Promise.all()
- 异步/等待
- 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);
}