Skip to content

Commit 991bfa5

Browse files
authored
Merge pull request #150 from sendbird/fix/promise-allSettled
fix(CLNP-1564): add promise polyfill for hermes
2 parents 62f958d + 2f31a45 commit 991bfa5

File tree

3 files changed

+207
-0
lines changed

3 files changed

+207
-0
lines changed
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import { PromisePolyfill } from '../../utils/promise';
2+
3+
describe('PromisePolyfill', () => {
4+
describe('allSettled', () => {
5+
it('primitive', async () => {
6+
const data = [1, NaN, '3', false, true, null, undefined, Symbol('symbol')];
7+
8+
const expected = await Promise.allSettled(data);
9+
const received = await PromisePolyfill.allSettled(data);
10+
11+
expect(expected).toEqual(received);
12+
});
13+
14+
it('object', async () => {
15+
const data = [
16+
{ 1: 1 },
17+
{ then: 'then' },
18+
[],
19+
[1, 2, 3],
20+
new Date(),
21+
function named() {},
22+
() => {},
23+
Promise.resolve(() => {}),
24+
Promise.reject(function () {}),
25+
];
26+
27+
const expected = await Promise.allSettled(data);
28+
const received = await PromisePolyfill.allSettled(data);
29+
30+
expect(expected).toEqual(received);
31+
});
32+
33+
it('promise', async () => {
34+
const data = [Promise.resolve([1, 2, 3]), Promise.reject([4, 5, 6]), [7, 8, 9]];
35+
36+
const expected = await Promise.allSettled(data);
37+
const received = await PromisePolyfill.allSettled(data);
38+
39+
expect(expected).toEqual(received);
40+
});
41+
});
42+
43+
describe('apply', () => {
44+
it('should not equal native Promise.allSettled', () => {
45+
expect(Promise.allSettled).not.toBe(PromisePolyfill.allSettled);
46+
});
47+
48+
it('should not affect native Promise.allSettled if already defined', () => {
49+
expect(Promise.allSettled).not.toBeUndefined();
50+
51+
PromisePolyfill.apply();
52+
expect(Promise.allSettled).not.toBe(PromisePolyfill.allSettled);
53+
});
54+
55+
it('should apply the polyfill to native Promise.allSettled if not defined', () => {
56+
// @ts-expect-error
57+
Promise.allSettled = undefined;
58+
59+
PromisePolyfill.apply();
60+
expect(Promise.allSettled).toBe(PromisePolyfill.allSettled);
61+
});
62+
});
63+
});

packages/uikit-react-native/src/index.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ import { Platform } from 'react-native';
22

33
import { Logger } from '@sendbird/uikit-utils';
44

5+
import { PromisePolyfill } from './utils/promise';
6+
57
/** Components **/
68
export { default as ChannelInput } from './components/ChannelInput';
79
export { default as ChannelMessageList } from './components/ChannelMessageList';
@@ -137,3 +139,6 @@ export * from './types';
137139

138140
Logger.setLogLevel(__DEV__ ? 'warn' : 'none');
139141
Logger.setTitle(`[UIKIT_${Platform.OS}]`);
142+
143+
// NOTE: In Hermes, not all implementations of Promise are included
144+
PromisePolyfill.apply();
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
import 'react-native';
2+
3+
/**
4+
* This file contains subcomponents with separate copyright notices and license terms.
5+
* Your use of the source code for these subcomponents is subject to the terms and conditions of the following licenses.
6+
*
7+
* MIT License: https://github.com/then/promise
8+
*
9+
* Copyright (c) 2014 Forbes Lindesay
10+
*
11+
* Permission is hereby granted, free of charge, to any person obtaining a copy
12+
* of this software and associated documentation files (the "Software"), to deal
13+
* in the Software without restriction, including without limitation the rights
14+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
15+
* copies of the Software, and to permit persons to whom the Software is
16+
* furnished to do so, subject to the following conditions:
17+
*
18+
* The above copyright notice and this permission notice shall be included in
19+
* all copies or substantial portions of the Software.
20+
*
21+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
22+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
24+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
26+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
27+
* THE SOFTWARE.
28+
*
29+
* Original code: https://github.com/then/promise/blob/master/src/es6-extensions.js
30+
* */
31+
let iterableToArray = function <T>(iterable: Iterable<T | PromiseLike<T>>): (T | PromiseLike<T>)[] {
32+
if (typeof Array.from === 'function') {
33+
// ES2015+, iterables exist
34+
iterableToArray = Array.from;
35+
return Array.from(iterable);
36+
}
37+
38+
// ES5, only arrays and array-likes exist
39+
iterableToArray = function (x) {
40+
return Array.prototype.slice.call(x);
41+
};
42+
return Array.prototype.slice.call(iterable);
43+
};
44+
45+
function onSettledFulfill<T>(value: T) {
46+
return { status: 'fulfilled', value: value } as const;
47+
}
48+
49+
function onSettledReject<T>(reason: T) {
50+
return { status: 'rejected', reason: reason } as const;
51+
}
52+
53+
function mapAllSettled<T>(item: T | PromiseLike<T>): PromiseLike<PromiseSettledResult<T>> | PromiseSettledResult<T> {
54+
if (item && (typeof item === 'object' || typeof item === 'function')) {
55+
if (item instanceof Promise && item.then === Promise.prototype.then) {
56+
return item.then(onSettledFulfill, onSettledReject);
57+
}
58+
// @ts-expect-error
59+
const then = item.then;
60+
if (typeof then === 'function') {
61+
return new Promise<T>(then.bind(item)).then(onSettledFulfill, onSettledReject);
62+
}
63+
}
64+
65+
// @ts-expect-error
66+
return onSettledFulfill(item);
67+
}
68+
69+
function getAggregateError(errors: unknown[]) {
70+
if (typeof AggregateError === 'function') {
71+
return new AggregateError(errors, 'All promises were rejected');
72+
}
73+
74+
const error = new Error('All promises were rejected') as Error & { errors: unknown[] };
75+
76+
error.name = 'AggregateError';
77+
error.errors = errors;
78+
79+
return error;
80+
}
81+
82+
export const PromisePolyfill = {
83+
allSettled<T extends readonly unknown[] | []>(values: T) {
84+
return Promise.all(iterableToArray(values).map(mapAllSettled)) as Promise<{
85+
-readonly [P in keyof T]: PromiseSettledResult<Awaited<T[P]>>;
86+
}>;
87+
},
88+
race<T>(values: Iterable<T | PromiseLike<T>>): Promise<Awaited<T>> {
89+
return new Promise(function (resolve, reject) {
90+
iterableToArray(values).forEach(function (value) {
91+
Promise.resolve(value).then(resolve, reject);
92+
});
93+
});
94+
},
95+
any<T>(values: Iterable<T | PromiseLike<T>>): Promise<Awaited<T>> {
96+
return new Promise<Awaited<T>>(function (resolve, reject) {
97+
const promises = iterableToArray(values);
98+
let hasResolved = false;
99+
const rejectionReasons: unknown[] = [];
100+
101+
function resolveOnce(value: Awaited<T>) {
102+
if (!hasResolved) {
103+
hasResolved = true;
104+
resolve(value);
105+
}
106+
}
107+
108+
function rejectionCheck(reason: unknown) {
109+
rejectionReasons.push(reason);
110+
111+
if (rejectionReasons.length === promises.length) {
112+
reject(getAggregateError(rejectionReasons));
113+
}
114+
}
115+
116+
if (promises.length === 0) {
117+
reject(getAggregateError(rejectionReasons));
118+
} else {
119+
promises.forEach(function (value) {
120+
Promise.resolve(value).then(resolveOnce, rejectionCheck);
121+
});
122+
}
123+
});
124+
},
125+
apply() {
126+
// https://github.com/facebook/react-native/blob/main/packages/react-native/Libraries/Utilities/PolyfillFunctions.js
127+
if (typeof global !== 'undefined' && 'Promise' in global) {
128+
if (!global.Promise.allSettled) {
129+
global.Promise.allSettled = this.allSettled;
130+
}
131+
if (!global.Promise.any) {
132+
global.Promise.any = this.any;
133+
}
134+
if (!global.Promise.race) {
135+
global.Promise.race = this.race;
136+
}
137+
}
138+
},
139+
};

0 commit comments

Comments
 (0)