SPFX - calling async function - Missing properties
-
08-02-2021 - |
Question
I'm trying to create a modern webpart that connects to MSGraphClient using the UI Fabric, to create quick access to files.
I'm getting the follow error calling an async function with _allItems
(property) AatResources._allItems: IDocument[] Type 'Promise' is missing the following properties from type 'IDocument[]': length, pop, push, concat, and 16 more.ts(2740)
Any help would be much appreciated
so my code
export interface IDocument {
key: string;
name: string;
value: string;
iconName: string;
webUrl:string;
fileType: string;
modifiedBy: string;
dateModified: string;
dateModifiedValue: number;
fileSize: string;
fileSizeRaw: number;
contentType:string;
parentId:string;
parentDriveId:string;
}
export default class AatResources extends React.Component<IAatResourcesProps, IAatResourcesState> {
private _allItems: IDocument[];
constructor(props: IAatResourcesProps) {
super(props);
this._allItems = _getDocuments('shared', default_dir_id , default_drive_id, '', '');
}
// render, etc...
}
The async function
MSGraph class is from https://www.techmikael.com/2018/09/example-of-wrapper-to-ease-usage-of.html
async function _getDocuments(drive_type: any, dir_id: any, drive_id: any, action: any, item_id: any) : Promise<IDocument[]> {
const items: IDocument[] = [];
let graphUrl :string;
await MSGraph.Init(this.context);
if (drive_type == 'me' && !dir_id) {
graphUrl = "/me/drive/root/children";
}
let response = await MSGraph.Get(graphUrl);
if (response) {
var contentType;
response.value.map((item: any) => {
if(item.folder){
contentType = 'folder';
}else{
contentType = 'file';
}
items.push({
key:item.id,
name:item.name,
value:item.name,
iconName:'',
fileType: '',
webUrl:item.webUrl,
fileSize: item.size,
fileSizeRaw: item.size,
dateModified: item.lastModifiedDateTime,
modifiedBy: item.lastModifiedBy.user.displayName,
dateModifiedValue: item.lastModifiedDateTime,
contentType:contentType,
parentId:item.parentReference.id,
parentDriveId:item.parentReference.driveId,
});
});
this.setState({
items: items
});
return items
}
}
alternatively using a sync function with a callback, I don't get the error but I'm having trouble returning the items array
function _getDocuments(drive_type: any, dir_id: any, drive_id: any, action: any, item_id: any) {
let graphUrl :string;
const items: IDocument[] = [];
if (drive_type == 'me' && !dir_id) {
graphUrl = "/me/drive/root/children";
}
this.props.context.msGraphClientFactory
.getClient()
.then((client: MSGraphClient): void => {
client
.api(graphUrl)
.version("v1.0")
.get((error, response: any, rawResponse?: any) => {
if (error) {
console.error(error);
return;
}
if (response) {
//console.log(response);
//var driveItems: Array<IDocument> = new Array<IDocument>();
var contentType;
response.value.map((item: any) => {
if(item.folder){
contentType = 'folder';
}else{
contentType = 'file';
}
items.push({
key:item.id,
name:item.name,
value:item.name,
iconName:'',
fileType: '',
webUrl:item.webUrl,
fileSize: item.size,
fileSizeRaw: item.size,
dateModified: item.lastModifiedDateTime,
modifiedBy: item.lastModifiedBy.user.displayName,
dateModifiedValue: item.lastModifiedDateTime,
contentType:contentType,
parentId:item.parentReference.id,
parentDriveId:item.parentReference.driveId,
});
});
//add callback ?
}
});
});
this.setState({
items: items
});
return items;
}
What I don't get, if I call the example function from Microsoft (https://developer.microsoft.com/en-us/fabric#/controls/web/detailslist) it doesn't error or say there's a missing promise, it works fine!
function _getDocuments(){
const items: IDocument[] = [];
for (let i = 0; i < 500; i++) {
const randomDate = _randomDate(new Date(2012, 0, 1), new Date());
const randomFileSize = '0';
const randomFileType = _randomFileIcon();
let fileName = 'test1';
fileName = fileName.charAt(0).toUpperCase() + fileName.slice(1).concat(`.${randomFileType.docType}`);
let userName = 'un';
userName = userName
.split(' ')
.map((name: string) => name.charAt(0).toUpperCase() + name.slice(1))
.join(' ');
items.push({
key: i.toString(),
name: fileName,
value: fileName,
iconName: randomFileType.url,
fileType: randomFileType.docType,
webUrl:'',
modifiedBy: userName,
dateModified: randomDate.dateFormatted,
dateModifiedValue: randomDate.value,
fileSize: '0',
fileSizeRaw: 0,
contentType:'',
parentId:'',
parentDriveId:'',
});
}
return items;
}
I've tried removing the parameters and testing it by declaring within the function, but still errors.
Solution 2
I resolved this by moving it in to a public modifier
public async _getDocuments(....
I also had to bind 'this' as this.props.context.msGraphClientFactory was undefined using
import autobind from 'react-autobind';
then adding in the class
constructor(props: IProps) {
super(props);
autobind(this)
...
OTHER TIPS
In your async version, you are returning the items array (of type IDocument[]
), but an async function needs to return a promise. The promise can contain a reference to the populated array.
React will handle it for you, if you specify async
, but then also specify the return type to be a typed Promise
. It will take your returned array and wrap it in a promise for you. In your case, you want to set return type of _getDocuments
to Promise<IDocument[]>
, so your declaration would look something like this:
async function _getDocuments(drive_type: any, dir_id: any, drive_id: any, action: any, item_id: any) : Promise<IDocument[]> {
In addition, in order for React to be able to interpret your return state as the resolving of a Promise, you first have to return the Promise, then you can return the results. Give this version a try:
function _getDocuments(drive_type: any, dir_id: any, drive_id: any, action: any, item_id: any) : Promise<IDocument[]> {
let graphUrl :string;
const items: IDocument[] = [];
if (drive_type == 'me' && !dir_id) {
graphUrl = "/me/drive/root/children";
}
return this.props.context.msGraphClientFactory
.getClient()
.then((client: MSGraphClient): void => {
client
.api(graphUrl)
.version("v1.0")
.get((error, response: any, rawResponse?: any) => {
if (error) {
console.error(error);
}
if (response) {
//console.log(response);
//var driveItems: Array<IDocument> = new Array<IDocument>();
var contentType;
response.value.map((item: any) => {
if(item.folder){
contentType = 'folder';
}else{
contentType = 'file';
}
items.push({
key:item.id,
name:item.name,
value:item.name,
iconName:'',
fileType: '',
webUrl:item.webUrl,
fileSize: item.size,
fileSizeRaw: item.size,
dateModified: item.lastModifiedDateTime,
modifiedBy: item.lastModifiedBy.user.displayName,
dateModifiedValue: item.lastModifiedDateTime,
contentType:contentType,
parentId:item.parentReference.id,
parentDriveId:item.parentReference.driveId,
});
});
this.setState({
items: items
});
return items;
}
});
});
}
Note the first return happens where you initiate the asynchronous call to the Graph API, this allows your function to return quickly and continue performing the rest of the operation as normal. Then, once you have assembled your result set, you can return your items
, which fulfills the promise.