Using PnPJS and Async/Await to Really Simplify Your API Calls

Anyone who knows us at Sympraxis realizes that Julie (@jfj1997) is the master developer. I consider myself quite capable, but Julie is always at least three steps ahead of me. She often leads me by the nose to a better pattern or practice, and it always seems to be a good idea.

Over the last few days – at Julie’s suggestion – I finally decided to try using the PnPJS library with a couple of SPFx Web Parts I’m working on. Even though I was on all the calls in the beginning of the effort to build it, and on some level it’s a “modern” SPServices, I decided a while back that I didn’t really like the way it looked. I understand REST just fine, I figured. Why would I need a helper library?

I’ve been cruising along just fine, building great stuff for our clients, but Julie started telling me recently that PnPJS and async/await was reducing her code size significantly. It sounded too good to be true, frankly. So I figured I should give it a try.

Here are a couple of examples to demonstrate how right Julie was and how much code I’ve been writing that I didn’t need to write. (Allow me to get away without doing everything I need to in the try/catch here. That’s not the point I’m trying to make. Watch for a post on Julie’s blog about how to use PnPJS’s Logging package with try/catch.)

Calling Search to Get Sites

In this instance, switching to PnPJS and async/await didn’t greatly diminish my code size, but I do think it makes it more readable. It also runs significantly faster.

Here’s the starting code. It’s a simple call to the search endpoint requesting Sites (as in Site Collections in the old parlance) with some specific exclusions. The Web Part this is in shows all the sites the current user has permissions to see. It’s like the modern Sites Web Part, but that Web Part doesn’t allow us to:

  1. Show *all* the sites the user can access, and
  2. Control the styling of the output

The end result is a listing of the sites, showing their icon and Title as a link to the site.

This is one of the data calls as it started:

  private _getSiteData(): Promise<any> {

    var thisDomain: string = location.host.split(".")[0];
    var exclusions: string[] = ["https://" + thisDomain + "-my.sharepoint.com", "https://" + thisDomain + ".sharepoint.com/portals/personal"];
    var exclusionString: string = " -Path:" + exclusions.join(" -Path:");
    exclusionString += " -Path=https://" + thisDomain + ".sharepoint.com";

    var url: string = this.context.pageContext.web.absoluteUrl +
      "/_api/search/query" +
      "?querytext='contentclass:sts_site " + exclusionString + "'" +
      "&selectproperties='Title,Path,SiteLogo'" +
      "&rowlimit=500";

    return this.context.spHttpClient.get(url, SPHttpClient.configurations.v1,{
      headers: {
        "Accept": "application/json;odata.metadata=none"
      }
    })
      .then((response: SPHttpClientResponse) => {
        return response.json();
      });
  }

And this is what it looks like using PnPJs and async/await.

  private async _getSiteData(): Promise<ISPSite[]> {

    var thisDomain: string = location.host.split(".")[0];
    var exclusions: string[] = ["https://" + thisDomain + "-my.sharepoint.com", "https://" + thisDomain + ".sharepoint.com/portals/personal"];
    var exclusionString: string = " -Path:" + exclusions.join(" -Path:");
    exclusionString += " -Path=https://" + thisDomain + ".sharepoint.com";

    try {

      let result = await sp.search(<SearchQuery>{
        Querytext: "contentclass:sts_site " + exclusionString,
        RowLimit: 500,
        SelectProperties: ["Title", "Path", "SiteLogo"]
      });

      return this.processSearchResults(result);

    } catch (e) {

      console.error(e);
      return null;

    }
    
  }

Even with the added try/catch the code is smaller, and since we’re building up the request with the library, we get help from our IDE (I use WebStorm) because the library is strictly typed. In the first example, since I’m building the request in the URL string, it’s easier to type an error into my code.

Getting List Data

In this example, I’m retrieving values from what I think of as a reference list for a more full-fledged application-in-a-Web-Part.

public getShipmentStatuses(serviceProps: IServiceProperties): Promise<IStatus[]> {
  return new Promise((resolve, reject) => {
    var urlList: string = `${serviceProps.context.pageContext.web.absoluteUrl}/_api/web/lists/getbytitle('SL_ShippingStatuses')/RenderListDataAsStream`;
    var requestOptions = this.spHttpOptions.getShipmentStatuses;
    serviceProps.context.spHttpClient.post(urlList,
      SPHttpClient.configurations.v1,
      requestOptions
).then((responseList: SPHttpClientResponse): Promise<{ value: any }> => {
      return responseList.json();
    })
      .then((responseJson: any) => {
        var shipmentStatuses: IStatus[] = [];
        for (var i = 0; i < responseJson.Row.length; i++) {
          var thisRow = responseJson.Row[i];
          var thisShipmentStatus: IStatus = new Status();
          thisShipmentStatus.Id = thisRow.ID;
          thisShipmentStatus.Title = thisRow.Title;
          thisShipmentStatus.SortOrder = thisRow.SortOrder;
          thisShipmentStatus.Follows = thisRow.Follows;
          thisShipmentStatus.CanBeCancelled = thisRow.CanBeCancelled === "Yes";
          shipmentStatuses.push(thisShipmentStatus);
        }
        resolve(shipmentStatuses);
      })
      .catch((err) => {
        reject(err);
      });
  });
}

This turns into:

public async getShipmentStatuses(serviceProps: IServiceProperties): Promise<IStatus[]> {

    try {

      let items = await sp
        .web
        .lists
        .getByTitle("SL_ShippingStatuses")
        .items
        .select("Id", "Title", "SortOrder", "CanBeCancelled")
        .orderBy("SortOrder")
        .get(spODataEntityArray<Item, IStatus>(Item));

      return items;

    } catch (e) {
      
      console.error(e);
      return null;
      
    }
}

Much less dense and easier to follow, I think.

The other thing I’m taking advantage of in PnPJs here is Entity Merging. You can see how simple that is in line 12 above. Rather than building up my object in what amounts to a constructor, the Entity Merging package takes care of it for me. All I have to do is tell it what interface to use. (Above, it’s IStatus.)

Summary

Sometimes it’s important for old dogs to learn new tricks. In software development, it’s really a requirement. It didn’t take me anywhere as near as much time to take these steps forward, and I’m already seeing performance improvements and simpler code – both wins for my clients. If Julie recommends something – we should ALL listen!

Similar Posts

11 Comments

    1. I’ll argue that the “before” list example was not so clean. That mysterious 10 line loop has disappeared in the refactoring but this has nothing to do with PnP or async.

    1. @Tom:

      That’s a good read. I may switch to Code at some point, if only to be aligned with everyone else. When I first looked at code when it came out, Webstorm was so much further along, it was a no brainer to me. Since I get a free license to Webstorm as an MVP, it won the comparison. I find that it works great to build all modern SharePoint “stuff”, and I’m used to the way it helps me with the process.
      M.

      1. Switching IDEs can be hard. Sounds like this one person’s experience was that the key binding extensions and other tricks made it an easy transition for him. Love seeing Visual Studio extended in this lightweight, platform neutral, web-friendly way.

  1. Interesting snippet, thanks for sharing. async/await is fairly recent in the ECMAScript standard, but it seems it’s been available for a while in TypeScript.
    I guess I reacted with TypeScript the same way you reacted to PnP (“I understand JavaScript just fine”), and missed some opportunities.

  2. Hi Marc,

    great segue for the PnP-JS library. I used the holidays to get myself familiar with PnP-JS, since I’ve been using JSOM most of the time, just because
    a) I was used to it
    b) it just works

    In other projects I was using the REST-API just “as-is” and it already fealt lean and cool, so I was acting just like you: “what’s all the fuss about PnP-JS, I can handle those REST-calls just fine!”.

    But I like the promises-approach alot, actually so much, that I wrote a little extension to JSOM, so ExecuteQueryAsync will return a promise instead of using those callbacks :) But this approach with async & await looks even better than dealing with .then() … I will definitly pick that up on my next project!

    Once you wrap your head around PnP-JS is does make things easier, than just handling the REST-API yourself.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.