1

I am trying to send multiple strings to be processed by a function. The function executes a promise on each string that needs to be evaluated. Once all the promise responses are returned, I need to send that back as a multidimensional array, so that I can do things like sorting on the full results set.

I know this code is bad, but I hope this gives you an idea of what I am trying to do. Does anyone know how to correctly write this?

import {
    SPHttpClient,
    SPHttpClientResponse
  } from '@microsoft/sp-http';
  
export interface SitePages {
    value: Pages[];
  }
  
 export interface Pages {
    Title: string;
    Created: string;
    Description: string;
    BannerImageUrl: {
      Url: string
    };
    FileRef: string;
  }

export async function getListData(sites : string[]) : Promise<SitePages[]> {
    let result : Promise<SitePages[]> = await Promise.all (sites.forEach(site => {
        this.site.context.spHttpClient.get(site + "/_api/web/lists/GetByTitle('Site Pages')/Items?$filter=PromotedState eq 2&$orderby=Created desc&$expand=Properties&$select=Created,Title,Description,BannerImageUrl,FileRef", SPHttpClient.configurations.v1)
        .then((response: SPHttpClientResponse) => {
            return response.json();
        });
    })); 
    return result;
 };

UPDATED CODE

import { Version } from '@microsoft/sp-core-library';
import {
  BaseClientSideWebPart,
  IPropertyPaneConfiguration,
  PropertyPaneTextField
} from '@microsoft/sp-webpart-base';
import { escape } from '@microsoft/sp-lodash-subset';

import styles from './CustomNewsWebpartWebPart.module.scss';
import * as strings from 'CustomNewsWebpartWebPartStrings';

import {
  SPHttpClient,
  SPHttpClientResponse
} from '@microsoft/sp-http';

export interface SitePages {
  value: Pages[];
}

export interface Pages {
  Title: string;
  Created: string;
  Description: string;
  BannerImageUrl: {
    Url: string
  };
  FileRef: string;
}

export interface ICustomNewsWebpartWebPartProps {
  description: string;
  listField: string;
}

export let elems : any[] = [];

export default class CustomNewsWebpartWebPart extends BaseClientSideWebPart<ICustomNewsWebpartWebPartProps> {

  public render(): void {
    this.getSitesNews();
  }

  protected get dataVersion(): Version {
    return Version.parse('1.0'); 
  }

  protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
    return {
      pages: [
        {
          header: {
            description: strings.PropertyPaneDescription
          },
          groups: [
            {
              groupName: strings.BasicGroupName,
              groupFields: [
                PropertyPaneTextField('description', {
                  label: strings.DescriptionFieldLabel,
                  value : strings.DescriptionField
                }),
                PropertyPaneTextField('listField', {
                  label: strings.listFieldLabel,
                  value : strings.listFieldDefault,
                  multiline: true,
                  rows:26
                })
              ]
            }
          ]
        }
      ]
    };
  }

  private getSitesNews() : void {
    this.domElement.innerHTML = null;
    if(null != this.properties.listField){
      let splitsites = this.properties.listField.split(/\r?\n|\r|\n/g);
      let sites : string[] = [];
      splitsites.forEach(site => {
        if(site != ''){
          sites.push(site.trim());
        }
      });
      console.log(sites);
      this._getListData(sites)
      .then((response) => {
        let pages : any[] = [];
        response.forEach(spages => {           
          spages.value.forEach(spage => {
            pages.push(spage);
          })
        });
        pages.sort((a,b) => (a.Created < b.Created) ? 1 : ((b.Created < a.Created) ? -1 : 0));
        let output : string = `<h1>${this.properties.description}</h1>`;
        let i : number = 0;
        for(i; i < pages.length; i++){
          let itemPath : string = '';
          let itemCreated : string = '';
          let itemTitle : string = '';
          let itemDescription : string = '';
          let itemBannerImageUrl : string = '';
          pages[i].Created ? itemCreated = pages[i].Created : itemCreated = '';
          pages[i].FileRef ? itemPath = pages[i].FileRef : itemPath = '';
          pages[i].Title ? itemTitle = pages[i].Title : itemTitle = '';
          pages[i].Description ? itemDescription = pages[i].Description : itemDescription = '';
          pages[i].BannerImageUrl.Url ? itemBannerImageUrl = pages[i].BannerImageUrl.Url : itemBannerImageUrl = '';
          
          output += `
          <div id="${itemCreated}" class="${styles.newsDiv}">
              <table class="${styles.newsTable}">
                <tr>
                  <td><div class="${styles.newsItemBanner}" style="background-image:url('${itemBannerImageUrl}');"></div></td>
                </tr>
                </tr>
                  <td>
                    <div class="${styles.newsItemContent}">
                      <h2>${itemTitle}</h2>
                      <p>${itemDescription}</p>
                      <!-- <p>${itemCreated}</p>-->
                      <a href="${itemPath}" class=${styles.newsButton}>Read More</a>
                    </div>
                  </td>
                </tr>
              </table>
            </div>
          `;
        }     
        this.domElement.innerHTML = output;
      })
      .catch((error) => {
        console.log(error);
      });
    }
  } 
  
  private async _getListData(spsites : string[]): Promise<SitePages[]> {
    return await Promise.all(spsites.map(spsite => {
      return this.context.spHttpClient.get(spsite + "/_api/web/lists/GetByTitle('Site Pages')/Items?$filter=PromotedState eq 2&$orderby=Created desc&$expand=Properties&$select=Created,Title,Description,BannerImageUrl,FileRef", SPHttpClient.configurations.v1)
      .then((response: SPHttpClientResponse) => {
        return response.json();
      })
      .catch((error) => {
        console.log(error);
      });
    }));
  }
 
}
Will73
  • 55
  • 7
  • 1
    Replace `forEach` with `map` and make the map callback function to actually return a promise. Well just by adding `return` before `this.site.context....`. And that is all i think. – Sergey Sosunov Feb 27 '23 at 21:42

1 Answers1

2

Promise.all takes an array of Promises, which your sites.forEach call doesn't provide. Change it from forEach to map and return the Promises:

await Promise.all(sites.map(site => {
  return this.site.context.spHttpClient.get( ... )
    .then((response: SPHttpClientResponse) => {
      return response.json();
    })
  })
)
ray
  • 26,557
  • 5
  • 28
  • 27
  • 1
    Also since there's an await the type would be `let result : SitePages[] =` – apokryfos Feb 27 '23 at 21:44
  • Thank you, I think that got me a little further. I guess I don't understand why map worked and the foreach did not. I'll do some research on that one though. This is what I get now though when I call the function from another file and try to console log it. Promise {: TypeError: Cannot read properties of undefined (reading 'context') – Will73 Feb 28 '23 at 12:39
  • The key difference between map and forEach here is that [map returns an array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map), forEach doesn't. map's purpose is to transform the array, like here, where you're turning a list of sites into a list of request promises. – ray Feb 28 '23 at 15:51
  • Thanks that does make sense. Unfortunately I can't seem to get the function to return anything now and I just get the undefined error. Any suggestions? – Will73 Feb 28 '23 at 17:27
  • Without seeing your code my guess is that you're not returning the request promise. Don't confuse this with the `return response.json()`--you need to return the top level promise from the `spHttpClient.get` call. `return this.site.context...` – ray Feb 28 '23 at 19:19
  • Not sure exactly what I was doing wrong, but I ended up putting the function back into the main file again. Seems to work this way. I've added the updated code above which will pull news pages from specific sites in an SP 2019 web application and put them in a single webpart sorted descending by date. Putting the code out here in case someone else want to use it. Thanks again for all your help and getting this working! – Will73 Mar 01 '23 at 11:36