首页 > 解决方案 > TypeScript:使用 Promise.all 替换图像源时的行为不一致

问题描述

对于我的 SPFx webpart(使用 React/TypeScript),我正在尝试替换某些图像的源,因为必须首先使用另一个 Microsoft Graph API 调用来检索最初获取的源。

我获取 HTML 中的所有图像,稍后将使用这些图像进行渲染temporalDivElement.querySelectorAll("img"),然后检查它们是否需要替换源。如果是这种情况,我会调用 Graph API 来获取新的图像源(或替换 span 节点,如果无法获取图像)。因为我必须遍历所有图像,所以我首先将所有这些对 Graph API 的请求收集到一个 Promises 数组中,然后使用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);
        });
    });
  }

这段代码有时会起作用,有时根本不起作用,有时它适用于一半的图像而不是另一半。例如,我在其中包含内容图像的相同两个频道回复上使用它,有时我得到两个图像,然后我只得到一个图像而另一个根本没有被替换(甚至没有带有更换失败的信息)或两者均未处理。就像有时承诺没有得到执行,或者至少没有在渲染之前的正确时间执行。

我看不出我的代码有什么问题,但我想这里有一些时间问题?

标签: javascriptreactjstypescriptes6-promisespfx

解决方案


如果我找到了解决此问题的方法,有人联系了我。

令我惊讶的是,我不幸忘记了我发布了这个问题,但从好的方面来说,我似乎在一天后解决了这个问题。

我记不太清了,但是查看新代码时,我认为这确实是一个时间问题,或者更详细地说,该问题来自于尝试解决forEach循环内的承诺。

我还不是很精通 JS/React,但据我了解,promise 也是异步的,所以我之前使用的这段代码是一个很大的禁忌:

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

自从进行此更改后,加载托管内容的问题就消失了。


推荐阅读