5

I'm in the process of getting my feet wet with SPFx Application Customizers and have a question regarding the use of promises in TS.

Before my onRender() method is called inside of onInit(), I need to call two functions using PnP JS:

  • The first retrieves the web title
  • The second fetches some user-profile metadata

How can I ensure these two functions complete before the onRender() logic is executed?

    public onInit(): Promise<void> {
        Log.info(LOG_SOURCE, `Initialized ${strings.Title}`);

        pnp.sp.web.select("Title").get().then(w => {
          this._webTitle = w.Title;
        });

        pnp.sp.profiles.myProperties.get().then(function (result) {
          var userProperties = result.UserProfileProperties;
          console.log(userProperties);
          userProperties.forEach(function (property) {
            if (property.Key == "PreferredName") {
              this._userDisplayName = property.Value;
              console.log('user display name: ' + this._userDisplayName);
            }
          });
        }).catch(function (error) {
          console.log("Error: " + error);
        });
    return Promise.resolve<void>();
  }

Currently, I have a race-condition issue where my application customizer is rendered before getting a response in my onInit() functions.

2 Answers 2

7

Batching the 2 requests as suggested by Venkat Konjeti is a great idea, but the question of how to delay your render until these are finished is demonstrated in the jquery-application-toastr sample (full disclosure, this is my sample).

Specifically in the SpfxToastrApplicationCustomizer.ts file. In the webpart class there is a private promise property. This is set in the onInit function when the call to go retrieve the data is made. However, there is no handler (then) at the time.

Later, in the onRender method once things are ready, the then is finally applied and handled. This solves the race condition. Promises will allow you to apply the then function before or after they are resolved. If they haven't resolved then your then won't be called until it is, if the promise already completed, your then will simply receive the results when you're ready for them.

Here's an example (borrowing the batch function from Venkat Konjeti's answer):

private myPromise: Promise<any>;

public onInit(): Promise<void> {
    this.myPromise = this.getData()
}

public onRender(): void {
    this.myPromise.then((results: any) => {
        //Do some rendering stuff
    });
}

function getData() {
    let batch = $pnp.sp.createBatch();

    $pnp.sp.web.select("Title").inBatch(batch).get().then(w => {
        console.log(w.Title);
    });

    $pnp.sp.profiles.myProperties.inBatch(batch).get().then(result => {
        var userProperties = result.UserProfileProperties;
        console.log(userProperties);
        userProperties.forEach(function (property) {
            if (property.Key == "PreferredName") {
              this._userDisplayName = property.Value;
              console.log('user display name: ' + this._userDisplayName);
            }
        });
    });

    return batch.execute();
}
1
  • 1
    thanks for the help @theChristKent! After a few minor tweaks to the above code, I've got this working! Just a word of caution – sp-pnp-js has a bug in older versions that causes inner promises to not be resolved on batch.execute. This gave me some grief initially once transitioning to a CDN: github.com/SharePoint/PnP-JS-Core/issues/417
    – Haymak3r
    Commented Aug 7, 2017 at 16:01
1

I think you can use the batch function. Here is example JavaScript code converted to the pnp batch executions.

function rednderBody() {

    var promise = getMyWebDetails();
    promise.then(() => console.log("All done!"));

}

function getMyWebDetails() {
    var batch = $pnp.sp.createBatch();

    $pnp.sp.web.select("Title").inBatch(batch).get().then(w => {
        console.log(w.Title);
    });

    $pnp.sp.profiles.myProperties.inBatch(batch).get().then(result => {
        var userProperties = result.UserProfileProperties;
        console.log(userProperties);
        userProperties.forEach(function (property) {
            if (property.Key == "PreferredName") {
              this._userDisplayName = property.Value;
              console.log('user display name: ' + this._userDisplayName);
            }
        });
    });

    return batch.execute();
}

rednderBody();
3
  • this batches the two PNP calls together, but how do I integrate this with the promise associated with onInit()? These calls would execute as a batch, but still complete after the SharePoint Framework's onRender() function is called.
    – Haymak3r
    Commented Aug 4, 2017 at 16:03
  • Can you try this promise.then(() => {return Promise.resolve<void>();}); . I haven`t tried this but give a try. Commented Aug 4, 2017 at 16:08
  • This errors – "[ts] A function whose declared type is neither 'void' nor 'any' must return a value."
    – Haymak3r
    Commented Aug 4, 2017 at 20:18

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.