在承诺电话完成之前设置的状态

时间:2018-07-31 15:29:17

标签: javascript reactjs typescript

正如标题所示,我正在尝试使用Promise设置我的react组件的状态。很简单,对不对?也许不吧。当我尝试在promise中进行后续的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,
       });
    });
  }

这是AdwNewStory类,它进行了第二个ajax调用:

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;

2 个答案:

答案 0 :(得分: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),则此代码看起来可能更简洁。

答案 1 :(得分:0)

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

您可以重新考虑设置,并在创建实例时向AdwNewStory实例提供所需的信息,以便更好地控制操作流程,也可以将回调传递给{{1} },并允许它告诉您创建故事的时间,但这会导致您遇到反模式。