In modern JavaScript development, polyfills are essential for ensuring compatibility across various environments, especially when working with older versions of Node.js. One common issue developer face when using the SharePoint Framework (SPFx) is the lack of support for certain JavaScript features like Promise.prototype.finally
in Node.js 8.14.0. This article, titled “Fix Promise.prototype.finally
and Polyfill Issues in Node.js prior to 10.x for SPFx,” will guide you through fixing these issues by implementing necessary polyfills and applying them correctly in your SPFx project.
Understanding the Issue
When using promises in SPFx, you might encounter the following error:
Error - typescript - src\webparts\hfHomePage\HfHomePageWebPart.ts(31,27): error TS2339: Property 'finally' does not exist on type 'Promise<any>'.
This error indicates that the finally
method is not available in the current environment, which is a common issue with Node.js versions prior to 10.x.
Step-by-Step Solution
To resolve this, we’ll create and apply polyfills for missing JavaScript features, ensuring our SPFx project runs smoothly on Node.js 8.14.0.
Step 1: Create the Polyfills File
First, we need to create a file, polyfills.ts
, that includes the necessary polyfills for our project.
// polyfills.ts
import 'core-js/es6/symbol';
import 'core-js/es6/object';
import 'core-js/es6/function';
import 'core-js/es6/parse-int';
import 'core-js/es6/parse-float';
import 'core-js/es6/number';
import 'core-js/es6/math';
import 'core-js/es6/string';
import 'core-js/es6/date';
import 'core-js/es6/array';
import 'core-js/es6/regexp';
import 'core-js/es6/weak-map';
import 'core-js/es6/map';
import 'core-js/es6/set';
// Polyfill for Promise.prototype.finally
if (!Promise.prototype.finally) {
Promise.prototype.finally = function (callback) {
const P = this.constructor;
return this.then(
value => P.resolve(callback()).then(() => value),
reason => P.resolve(callback()).then(() => { throw reason })
);
};
}
// Polyfill for Array.prototype.includes
if (!Array.prototype.includes) {
Object.defineProperty(Array.prototype, 'includes', {
value: function (searchElement, fromIndex) {
if (this == null) {
throw new TypeError('"this" is null or not defined');
}
const o = Object(this);
const len = o.length >>> 0;
if (len === 0) {
return false;
}
const n = fromIndex | 0;
let k = Math.max(n >= 0 ? n : len - Math.abs(n), 0);
while (k < len) {
if (o[k] === searchElement) {
return true;
}
k++;
}
return false;
}
});
}
// Polyfill for String.prototype.includes
if (!String.prototype.includes) {
String.prototype.includes = function (search, start) {
'use strict';
if (typeof start !== 'number') {
start = 0;
}
if (start + search.length > this.length) {
return false;
} else {
return this.indexOf(search, start) !== -1;
}
};
}
// Polyfill for Object.assign
if (typeof Object.assign !== 'function') {
Object.assign = function (target) {
'use strict';
if (target == null) {
throw new TypeError('Cannot convert undefined or null to object');
}
target = Object(target);
for (let index = 1; index < arguments.length; index++) {
const source = arguments[index];
if (source != null) {
for (const key in source) {
if (Object.prototype.hasOwnProperty.call(source, key)) {
target[key] = source[key];
}
}
}
}
return target;
};
}
// Polyfill for Array.prototype.concat
if (!Array.prototype.concat) {
Array.prototype.concat = function () {
const result = [];
for (let i = 0; i < arguments.length; i++) {
if (Array.isArray(arguments[i])) {
result.push.apply(result, arguments[i]);
} else {
result.push(arguments[i]);
}
}
return result;
};
}
// Define the Polyfills class with the applyAll method
export class Polyfills {
static applyAll() {
// Apply the polyfills if not already applied
if (!Promise.prototype.finally) {
Promise.prototype.finally = function (callback) {
const P = this.constructor;
return this.then(
value => P.resolve(callback()).then(() => value),
reason => P.resolve(callback()).then(() => { throw reason })
);
};
}
if (!Array.prototype.includes) {
Object.defineProperty(Array.prototype, 'includes', {
value: function (searchElement, fromIndex) {
if (this == null) {
throw new TypeError('"this" is null or not defined');
}
const o = Object(this);
const len = o.length >>> 0;
if (len === 0) {
return false;
}
const n = fromIndex | 0;
let k = Math.max(n >= 0 ? n : len - Math.abs(n), 0);
while (k < len) {
if (o[k] === searchElement) {
return true;
}
k++;
}
return false;
}
});
}
if (!String.prototype.includes) {
String.prototype.includes = function (search, start) {
'use strict';
if (typeof start !== 'number') {
start = 0;
}
if (start + search.length > this.length) {
return false;
} else {
return this.indexOf(search, start) !== -1;
}
};
}
if (typeof Object.assign !== 'function') {
Object.assign = function (target) {
'use strict';
if (target == null) {
throw new TypeError('Cannot convert undefined or null to object');
}
target = Object(target);
for (let index = 1; index < arguments.length; index++) {
const source = arguments[index];
if (source != null) {
for (const key in source) {
if (Object.prototype.hasOwnProperty.call(source, key)) {
target[key] = source[key];
}
}
}
}
return target;
};
}
if (!Array.prototype.concat) {
Array.prototype.concat = function () {
const result = [];
for (let i = 0; i < arguments.length; i++) {
if (Array.isArray(arguments[i])) {
result.push.apply(result, arguments[i]);
} else {
result.push(arguments[i]);
}
}
return result;
};
}
}
}
Step 2: Import and Apply Polyfills
In your main entry point file (webpart.ts), import and apply the polyfills before any other code execution.
// Import and apply polyfills at the very beginning
import { Polyfills } from './polyfills';
Polyfills.applyAll();
// Import necessary SPFx modules
import { Version } from '@microsoft/sp-core-library';
import { IPropertyPaneConfiguration, PropertyPaneTextField } from '@microsoft/sp-property-pane';
import { BaseClientSideWebPart } from '@microsoft/sp-webpart-base';
import * as strings from 'HfHomePageWebPartStrings';
export interface IHfHomePageWebPartProps {
description: string;
}
export default class HfHomePageWebPart extends BaseClientSideWebPart<IHfHomePageWebPartProps> {
constructor() {
super();
Polyfills.applyAll(); // Ensure polyfills are applied
}
public render(): void {
this.domElement.innerHTML = `
<div class="${styles.hfHomePage}">
<div class="${styles.container}">
<div class="${styles.row}">
<div class="${styles.column}">
<span class="${styles.title}">Welcome to SharePoint!</span>
<p class="${styles.subTitle}">Customize SharePoint experiences using Web Parts.</p>
<p class="${styles.description}">${escape(this.properties.description)}</p>
<a href="https://aka.ms/spfx" class="${styles.button}">
<span class="${styles.label}">Learn more</span>
</a>
</div>
</div>
</div>
</div>`;
}
// Other functions like dataVersion, getPropertyPaneConfiguration will be included as per your code.
}
Step 3: Build and Deploy
Finally, build and deploy your project to ensure the polyfills are applied correctly:
gulp clean
gulp build
gulp bundle --ship
gulp package-solution --ship
Bonus
If you are using ES5, the finally
method will not be supported even after the above updates. To address this, you can create a file named common.d.ts
and add the following code:
interface Promise<T> {
finally(onfinally?: (() => void) | undefined | null): Promise<T>;
}
If you still face the error, check your core-js
version and update it to version 3 or above. If you do so, you will need to update your imports from core-js/es6
to core-js/es
.
Conclusion
By following these steps, you ensure that your SPFx project runs smoothly even on older Node.js versions like 8.14.0. Applying polyfills helps bridge the gap between modern JavaScript features and legacy environments, allowing you to use features like Promise.prototype.finally
without issues. This approach not only resolves current problems but also prepares your codebase for future compatibility challenges.
Reference
Promise.prototype.finally() – JavaScript | MDN (mozilla.org)
No Comment! Be the first one.