【问题标题】:State being set before promise call is finished在 Promise 调用完成之前设置的状态
【发布时间】:2018-08-01 00:08:01
【问题描述】:

正如标题所示,我正在尝试使用 promise 来设置我的 react 组件的状态。很简单,对吧?也许不吧。好吧,当我尝试在承诺中进行后续的 ajax 调用时,问题就出现了。

代码如下。如您所见,我正在尝试映射我返回的数据并创建一个新对象。在创建该对象时,还有另一个应设置其中一个字段的 promise 调用。所有这些都可以正常工作,但是在第二个承诺完成之前设置状态并且该字段对我不可用。

我尝试使用 async/await 让它等待调用完成,但我在这种方法中也没有成功。

如果您有任何建议,我们将不胜感激。

我在 ComponentDidMount 方法中进行调用:

public componentDidMount(): void {
    this._spApiService
      .getListItems(this.props.context, "Company News")
      .then((spNewsStories: SpNewsStory[]) => {
        return spNewsStories.map((newsStory: SpNewsStory) => {
          return new AdwNewsStory(newsStory, this.props.context);
        });
      })
      .then((adwNewsStories: AdwNewsStory[]) => {
        this.setState({
          topStories: adwNewsStories,
       });
    });
  }

这是进行第二次 ajax 调用的 AdwNewStory 类:

import { SpNewsStory } from "./SpNewsStory";
import { ISpApiService } from "../../interfaces/ISpApiService";
import SpApiService from "../../services/SpApiService";
import { WebPartContext } from "../../../node_modules/@microsoft/sp-webpart- 
base";
import { SpAttachment } from "../SpAttachment";
import IEnvironmentService from "../../interfaces/IEnvironmentService";
import EnvironmentService from "../../services/EnvironmentService";
import { IAdwDateTimeService } from "../../interfaces/IAdwDateTimeService";
import AdwDateTimeService from "../../services/AdwDateTimeService";

class AdwNewsStory {
  public id: number;
  public title: string;
  public publishDate: string;
  public storySummary: string;
  public storyLink: string;
  public windowTarget: string;
  public imageUrl: string;
  public imageAlternativeText: string;
  public attachments: boolean;

  private _spApiService: ISpApiService;
  private _context: WebPartContext;
  private _environmentService: IEnvironmentService;
  private _adwDateTimeService: IAdwDateTimeService;

  constructor(spNewsStory: SpNewsStory, context?: WebPartContext) {
    this._spApiService = new SpApiService();
    this._context = context;
    this._environmentService = new EnvironmentService();
    this._adwDateTimeService = new AdwDateTimeService();
    this.buildAdwNewsStory(spNewsStory);
  }

  private buildAdwNewsStory = (spNewsStory: SpNewsStory): void => {
    this.id = spNewsStory.Id;
    this.title = spNewsStory.Title;
    this.publishDate = this.setPublishDate(spNewsStory.PublishDate);
    this.storySummary = spNewsStory.StorySummary;
    this.storyLink = spNewsStory.Link.Description;
    this.windowTarget = spNewsStory.WindowTarget;
    this.imageAlternativeText = spNewsStory.ImageAlternateText;
    this.attachments = spNewsStory.Attachments;
    if (this.attachments) {
      this.setImageUrl();
    }
  };

  private setImageUrl = (): void => {
      this._spApiService.getListItemAttachments(this._context, "Company News", this.id).then(attachments => {
      const siteUrl: string = this._environmentService.getSiteUrl();
      const attchmentUrl: string = `${siteUrl}/Lists/Company%20News/Attachments/${this.id}/${attachments[0].FileName}`;
      this.imageUrl = attchmentUrl;
    });
  };

 private setPublishDate = (dateString: string) => {
    const publishDate: Date = new Date(dateString);
    return `${this._adwDateTimeService.getMonthName(publishDate.getMonth())} ${publishDate.getDate()}, ${publishDate.getFullYear()}`;
 };
}

export default AdwNewsStory;

【问题讨论】:

  • 我还没有完整的答案,但我会强烈敦促您重新考虑您的设计。虽然没有技术上的理由不这样做,但在构造函数中使用 Promises 可能不是一个好主意,因为无法从调用站点真正得知该 Promise 何时解决。
  • 这会直接导致您遇到的问题。
  • "在创建该对象时,还有另一个应设置字段的承诺调用" - 你永远不会等待那个承诺,是的。另外,just don't use promises inside constructors。首先获取您的附件(并明确链接您的承诺),然后构建对象。

标签: javascript reactjs typescript


【解决方案1】:

这里有几个问题。首先:

private setImageUrl = (): void => {
    // You aren't returning this promise here, so there's no way for
    // calling code to get notified when the promise resolves
    this._spApiService.getListItemAttachments(this._context, "Company News", this.id).then(attachments => {
        const siteUrl: string = this._environmentService.getSiteUrl();
        const attchmentUrl: string = `${siteUrl}/Lists/Company%20News/Attachments/${this.id}/${attachments[0].FileName}`;
        this.imageUrl = attchmentUrl;
    });
};

然而,更大的问题是您从构造函数中调用了该方法。您真的不希望构造函数进行异步调用。虽然没有技术上的理由不这样做,但实际上没有办法将该承诺返回给调用者。这会导致您遇到的错误。

我建议将 buildAdwNewsStory() 方法移出构造函数,并单独调用它,因为它涉及异步调用。

class AdwNewsStory {
    // ... fields...
    constructor(context?: WebPartContext) {
        this._spApiService = new SpApiService();
        this._context = context;
        this._environmentService = new EnvironmentService();
        this._adwDateTimeService = new AdwDateTimeService();
    }

    public buildAdwNewsStory(spNewsStory: SpNewsStory): Promise<void> {
        this.id = spNewsStory.Id;
        this.title = spNewsStory.Title;
        this.publishDate = this.setPublishDate(spNewsStory.PublishDate);
        this.storySummary = spNewsStory.StorySummary;
        this.storyLink = spNewsStory.Link.Description;
        this.windowTarget = spNewsStory.WindowTarget;
        this.imageAlternativeText = spNewsStory.ImageAlternateText;
        this.attachments = spNewsStory.Attachments;
        if (this.attachments) {
            return this.setImageUrl();
        } else {
            return Promise.resolve();
        }
    }
    ...
    private setImageUrl() {
        return this._spApiService.getListItemAttachments(this._context, "Company News", this.id)
            .then(attachments => {
                const siteUrl: string = this._environmentService.getSiteUrl();
                const attchmentUrl: string = `${siteUrl}/Lists/Company%20News/Attachments/${this.id}/${attachments[0].FileName}`;
                this.imageUrl = attchmentUrl;
            });
    }
}

然后从你的componentDidMount:

public componentDidMount(): void {
    this._spApiService
        .getListItems(this.props.context, "Company News")
        .then((spNewsStories: SpNewsStory[]) =>
            spNewsStories.map((newsStory: SpNewsStory) => {
                var story = new AdwNewsStory(this.props.context);
                return story.buildAdwNewsStory(newsStory);
            })
        )
        .then((adwNewsStories: AdwNewsStory[]) => {
            this.setState({
                topStories: adwNewsStories,
            });
        });
}

附带说明一下,如果您移至 async/await 而不是直接使用 Promise 方法 (.then/.catch),这段代码可能看起来会更简洁一些(.catch

【讨论】:

    【解决方案2】:

    在这种情况下,setState 在您的承诺解决之前触发,因为您的类的构造函数将立即返回,尽管调用了异步操作。

    您可以重新考虑您的设置,并在创建实例时为您的AdwNewStory 实例提供所需的信息,以便您更好地控制操作流程,或者您可以将回调传递给adwNewStory 并允许这样做在创建故事时告诉您,但这会导致您使用反模式。

    【讨论】:

      猜你喜欢
      • 2017-03-05
      • 1970-01-01
      • 1970-01-01
      • 2019-03-07
      • 2016-05-13
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多