TypeScript:使用 Promise.all 替换图像源时的行为不一致
TypeScript: Inconsistent behavior when replacing image source using Promise.all
对于我的 SPFx webpart(使用 React/TypeScript),我正在尝试替换一些图像的来源,因为必须首先使用另一个 Microsoft Graph API 调用来检索最初获取的来源。
我获取 HTML 中的所有图像,稍后将使用 temporalDivElement.querySelectorAll("img")
进行渲染,然后检查它们是否需要替换源。如果是这种情况,我会调用 Graph API 来获取新的图像源(或者替换跨度节点,如果无法获取图像)。因为我必须遍历所有图像,所以我首先将所有这些请求收集到 Graph API 中的 Promise 数组中,然后使用 Promise.all()
.
执行它们
这是我当前的代码:
public parseAndFixBodyContent(messageId: string, bodyContent: string) {
return new Promise<string>(async (resolve, reject) => {
let temporalDivElement = document.createElement("div");
temporalDivElement.innerHTML = bodyContent;
// For hosted content images: fetch hosted content with Graph API, convert to BLOB and then replace existing src attribute with BLOB value
const imgTags = temporalDivElement.querySelectorAll("img");
const hostedImagesReplacementPromises: Promise<void>[] = [];
imgTags.forEach((img) => {
if (img.src &&
img.src.indexOf(`/messages/${messageId}/hostedContents/`) > -1) {
// Hosted Content url found, try to fetch image through API
let hostedContentApiUrl: string = img.src;
hostedImagesReplacementPromises.push(this.replaceHostedContentImageSource(hostedContentApiUrl, img));
}
});
Promise.all(hostedImagesReplacementPromises)
.then(() => {
resolve(temporalDivElement.innerHTML);
});
});
}
public replaceHostedContentImageSource(hostedContentApiUrl: string, image: HTMLImageElement) {
return new Promise<void>(async (resolve, reject) => {
this.getHostedContentAsBlob(hostedContentApiUrl).then((imageBlobUrl) => {
image.src = imageBlobUrl;
resolve();
})
.catch(error => {
// We could not fetch the hosted content for the image
let missingImageInfo = document.createElement("span");
missingImageInfo.innerText = `(${strings.ImageNotAvailable})`;
image.parentNode.replaceChild(missingImageInfo, image);
resolve();
});
});
}
public getHostedContentAsBlob(hostedContentApiUrl: string) {
return new Promise<string>(async (resolve, reject) => {
this.context.msGraphClientFactory
.getClient()
.then((client: MSGraphClient): void =>{
client
.api(hostedContentApiUrl)
.version("beta")
.responseType('blob')
.get((error, response: Blob, rawResponse?: any) => {
if (rawResponse.status == 200 && response) {
const imageUrl: string = URL.createObjectURL(response);
resolve(imageUrl);
} else {
reject(new Error(strings.ErrorCouldNotGetHostedConent));
}
});
})
.catch(error => {
reject(error);
});
});
}
此代码有时有效,有时完全无效,有时对一半图像有效,对另一半图像无效。例如,我在相同的两个频道回复中使用它,其中包含内容图像,有时我得到两个图像,然后我只得到一个图像而另一个根本没有被替换(甚至没有替换 SPAN 标签替换失败的信息)或两者都没有被处理。就像有时承诺没有得到执行,或者至少没有在渲染前的正确时间执行。
我看不出我的代码有什么问题,但我想这里有一些时间问题?
有人联系我,如果我找到了解决这个问题的方法。
令我惊讶的是,不幸的是我忘记了我发布了这个问题,但从好的方面来看,我似乎在一天后解决了这个问题。
我记不太清了,但看了新代码,我相信这确实是一个时间问题,或者更详细地说,这个问题来自于试图解决 forEach
循环中的承诺。
我还不是很精通JS/React,但据我了解,promises 也是异步的,所以我之前使用的这段代码是一个很大的禁忌:
// Some message body content needs to be prepared for the display (fetch hosted content, etc.)
slicedChannelTopics.forEach((t) => {
this.parseAndFixBodyContent(t.message.id, t.message.content).then((transformedContent) => {
if (t.message.contentType && t.message.contentType == 'html' && t.message.content) {
t.message.content = transformedContent;
}
})
.catch(parseError => {
console.log(parseError);
});
});
我将其更改为首先收集所有承诺,然后使用 Promise.all(...)
:
解决它们
let promisesForParseAndFixBodyContent = topicReplies.map((reply) =>
{
return this.parseAndFixBodyContent(reply.message, MessageType.Reply);
});
Promise.all(promisesForParseAndFixBodyContent).then(() => {
resolve(topicReplies);
});
进行此更改后,加载托管内容的问题就消失了。
对于我的 SPFx webpart(使用 React/TypeScript),我正在尝试替换一些图像的来源,因为必须首先使用另一个 Microsoft Graph API 调用来检索最初获取的来源。
我获取 HTML 中的所有图像,稍后将使用 temporalDivElement.querySelectorAll("img")
进行渲染,然后检查它们是否需要替换源。如果是这种情况,我会调用 Graph API 来获取新的图像源(或者替换跨度节点,如果无法获取图像)。因为我必须遍历所有图像,所以我首先将所有这些请求收集到 Graph API 中的 Promise 数组中,然后使用 Promise.all()
.
这是我当前的代码:
public parseAndFixBodyContent(messageId: string, bodyContent: string) {
return new Promise<string>(async (resolve, reject) => {
let temporalDivElement = document.createElement("div");
temporalDivElement.innerHTML = bodyContent;
// For hosted content images: fetch hosted content with Graph API, convert to BLOB and then replace existing src attribute with BLOB value
const imgTags = temporalDivElement.querySelectorAll("img");
const hostedImagesReplacementPromises: Promise<void>[] = [];
imgTags.forEach((img) => {
if (img.src &&
img.src.indexOf(`/messages/${messageId}/hostedContents/`) > -1) {
// Hosted Content url found, try to fetch image through API
let hostedContentApiUrl: string = img.src;
hostedImagesReplacementPromises.push(this.replaceHostedContentImageSource(hostedContentApiUrl, img));
}
});
Promise.all(hostedImagesReplacementPromises)
.then(() => {
resolve(temporalDivElement.innerHTML);
});
});
}
public replaceHostedContentImageSource(hostedContentApiUrl: string, image: HTMLImageElement) {
return new Promise<void>(async (resolve, reject) => {
this.getHostedContentAsBlob(hostedContentApiUrl).then((imageBlobUrl) => {
image.src = imageBlobUrl;
resolve();
})
.catch(error => {
// We could not fetch the hosted content for the image
let missingImageInfo = document.createElement("span");
missingImageInfo.innerText = `(${strings.ImageNotAvailable})`;
image.parentNode.replaceChild(missingImageInfo, image);
resolve();
});
});
}
public getHostedContentAsBlob(hostedContentApiUrl: string) {
return new Promise<string>(async (resolve, reject) => {
this.context.msGraphClientFactory
.getClient()
.then((client: MSGraphClient): void =>{
client
.api(hostedContentApiUrl)
.version("beta")
.responseType('blob')
.get((error, response: Blob, rawResponse?: any) => {
if (rawResponse.status == 200 && response) {
const imageUrl: string = URL.createObjectURL(response);
resolve(imageUrl);
} else {
reject(new Error(strings.ErrorCouldNotGetHostedConent));
}
});
})
.catch(error => {
reject(error);
});
});
}
此代码有时有效,有时完全无效,有时对一半图像有效,对另一半图像无效。例如,我在相同的两个频道回复中使用它,其中包含内容图像,有时我得到两个图像,然后我只得到一个图像而另一个根本没有被替换(甚至没有替换 SPAN 标签替换失败的信息)或两者都没有被处理。就像有时承诺没有得到执行,或者至少没有在渲染前的正确时间执行。
我看不出我的代码有什么问题,但我想这里有一些时间问题?
有人联系我,如果我找到了解决这个问题的方法。
令我惊讶的是,不幸的是我忘记了我发布了这个问题,但从好的方面来看,我似乎在一天后解决了这个问题。
我记不太清了,但看了新代码,我相信这确实是一个时间问题,或者更详细地说,这个问题来自于试图解决 forEach
循环中的承诺。
我还不是很精通JS/React,但据我了解,promises 也是异步的,所以我之前使用的这段代码是一个很大的禁忌:
// Some message body content needs to be prepared for the display (fetch hosted content, etc.)
slicedChannelTopics.forEach((t) => {
this.parseAndFixBodyContent(t.message.id, t.message.content).then((transformedContent) => {
if (t.message.contentType && t.message.contentType == 'html' && t.message.content) {
t.message.content = transformedContent;
}
})
.catch(parseError => {
console.log(parseError);
});
});
我将其更改为首先收集所有承诺,然后使用 Promise.all(...)
:
let promisesForParseAndFixBodyContent = topicReplies.map((reply) =>
{
return this.parseAndFixBodyContent(reply.message, MessageType.Reply);
});
Promise.all(promisesForParseAndFixBodyContent).then(() => {
resolve(topicReplies);
});
进行此更改后,加载托管内容的问题就消失了。