diff --git a/src/GroupAddressPicker.js b/src/GroupAddressPicker.js
index 7da37b6df1..793f5c9227 100644
--- a/src/GroupAddressPicker.js
+++ b/src/GroupAddressPicker.js
@@ -21,6 +21,7 @@ import MultiInviter from './utils/MultiInviter';
import { _t } from './languageHandler';
import MatrixClientPeg from './MatrixClientPeg';
import GroupStore from './stores/GroupStore';
+import {allSettled} from "./utils/promise";
export function showGroupInviteDialog(groupId) {
return new Promise((resolve, reject) => {
@@ -118,7 +119,7 @@ function _onGroupInviteFinished(groupId, addrs) {
function _onGroupAddRoomFinished(groupId, addrs, addRoomsPublicly) {
const matrixClient = MatrixClientPeg.get();
const errorList = [];
- return Promise.all(addrs.map((addr) => {
+ return allSettled(addrs.map((addr) => {
return GroupStore
.addRoomToGroup(groupId, addr.address, addRoomsPublicly)
.catch(() => { errorList.push(addr.address); })
@@ -138,7 +139,7 @@ function _onGroupAddRoomFinished(groupId, addrs, addRoomsPublicly) {
groups.push(groupId);
return MatrixClientPeg.get().sendStateEvent(roomId, 'm.room.related_groups', {groups}, '');
}
- }).reflect();
+ });
})).then(() => {
if (errorList.length === 0) {
return;
diff --git a/src/autocomplete/Autocompleter.js b/src/autocomplete/Autocompleter.js
index af2744950f..c385e13878 100644
--- a/src/autocomplete/Autocompleter.js
+++ b/src/autocomplete/Autocompleter.js
@@ -27,6 +27,7 @@ import UserProvider from './UserProvider';
import EmojiProvider from './EmojiProvider';
import NotifProvider from './NotifProvider';
import Promise from 'bluebird';
+import {timeout} from "../utils/promise";
export type SelectionRange = {
beginning: boolean, // whether the selection is in the first block of the editor or not
@@ -77,23 +78,16 @@ export default class Autocompleter {
while the user is interacting with the list, which makes it difficult
to predict whether an action will actually do what is intended
*/
- const completionsList = await Promise.all(
- // Array of inspections of promises that might timeout. Instead of allowing a
- // single timeout to reject the Promise.all, reflect each one and once they've all
- // settled, filter for the fulfilled ones
- this.providers.map(provider =>
- provider
- .getCompletions(query, selection, force)
- .timeout(PROVIDER_COMPLETION_TIMEOUT)
- .reflect(),
- ),
- );
+ const completionsList = await Promise.all(this.providers.map(provider => {
+ return timeout(provider.getCompletions(query, selection, force), null, PROVIDER_COMPLETION_TIMEOUT);
+ }));
+
+ // map then filter to maintain the index for the map-operation, for this.providers to line up
+ return completionsList.map((completions, i) => {
+ if (!completions || !completions.length) return;
- return completionsList.filter(
- (inspection) => inspection.isFulfilled(),
- ).map((completionsState, i) => {
return {
- completions: completionsState.value(),
+ completions,
provider: this.providers[i],
/* the currently matched "command" the completer tried to complete
@@ -102,6 +96,6 @@ export default class Autocompleter {
*/
command: this.providers[i].getCurrentCommand(query, selection, force),
};
- });
+ }).filter(Boolean);
}
}
diff --git a/src/components/structures/GroupView.js b/src/components/structures/GroupView.js
index 4056557a7c..776e7f0d6d 100644
--- a/src/components/structures/GroupView.js
+++ b/src/components/structures/GroupView.js
@@ -38,7 +38,7 @@ import FlairStore from '../../stores/FlairStore';
import { showGroupAddRoomDialog } from '../../GroupAddressPicker';
import {makeGroupPermalink, makeUserPermalink} from "../../utils/permalinks/Permalinks";
import {Group} from "matrix-js-sdk";
-import {sleep} from "../../utils/promise";
+import {allSettled, sleep} from "../../utils/promise";
const LONG_DESC_PLACEHOLDER = _td(
`
HTML for your community's page
@@ -99,11 +99,10 @@ const CategoryRoomList = createReactClass({
onFinished: (success, addrs) => {
if (!success) return;
const errorList = [];
- Promise.all(addrs.map((addr) => {
+ allSettled(addrs.map((addr) => {
return GroupStore
.addRoomToGroupSummary(this.props.groupId, addr.address)
- .catch(() => { errorList.push(addr.address); })
- .reflect();
+ .catch(() => { errorList.push(addr.address); });
})).then(() => {
if (errorList.length === 0) {
return;
@@ -276,11 +275,10 @@ const RoleUserList = createReactClass({
onFinished: (success, addrs) => {
if (!success) return;
const errorList = [];
- Promise.all(addrs.map((addr) => {
+ allSettled(addrs.map((addr) => {
return GroupStore
.addUserToGroupSummary(addr.address)
- .catch(() => { errorList.push(addr.address); })
- .reflect();
+ .catch(() => { errorList.push(addr.address); });
})).then(() => {
if (errorList.length === 0) {
return;
diff --git a/src/components/structures/TimelinePanel.js b/src/components/structures/TimelinePanel.js
index faa6f2564a..3dd5ea761e 100644
--- a/src/components/structures/TimelinePanel.js
+++ b/src/components/structures/TimelinePanel.js
@@ -1064,8 +1064,6 @@ const TimelinePanel = createReactClass({
});
};
- let prom = this._timelineWindow.load(eventId, INITIAL_SIZE);
-
// if we already have the event in question, TimelineWindow.load
// returns a resolved promise.
//
@@ -1074,9 +1072,13 @@ const TimelinePanel = createReactClass({
// quite slow. So we detect that situation and shortcut straight to
// calling _reloadEvents and updating the state.
- if (prom.isFulfilled()) {
+ const timeline = this.props.timelineSet.getTimelineForEvent(eventId);
+ if (timeline) {
+ // This is a hot-path optimization by skipping a promise tick
+ // by repeating a no-op sync branch in TimelineSet.getTimelineForEvent & MatrixClient.getEventTimeline
onLoaded();
} else {
+ const prom = this._timelineWindow.load(eventId, INITIAL_SIZE);
this.setState({
events: [],
liveEvents: [],
@@ -1084,11 +1086,8 @@ const TimelinePanel = createReactClass({
canForwardPaginate: false,
timelineLoading: true,
});
-
- prom = prom.then(onLoaded, onError);
+ prom.then(onLoaded, onError);
}
-
- prom.done();
},
// handle the completion of a timeline load or localEchoUpdate, by
diff --git a/src/rageshake/rageshake.js b/src/rageshake/rageshake.js
index d61956c925..ee1aed2294 100644
--- a/src/rageshake/rageshake.js
+++ b/src/rageshake/rageshake.js
@@ -136,6 +136,8 @@ class IndexedDBLogStore {
this.id = "instance-" + Math.random() + Date.now();
this.index = 0;
this.db = null;
+
+ // these promises are cleared as soon as fulfilled
this.flushPromise = null;
// set if flush() is called whilst one is ongoing
this.flushAgainPromise = null;
@@ -208,15 +210,15 @@ class IndexedDBLogStore {
*/
flush() {
// check if a flush() operation is ongoing
- if (this.flushPromise && this.flushPromise.isPending()) {
- if (this.flushAgainPromise && this.flushAgainPromise.isPending()) {
- // this is the 3rd+ time we've called flush() : return the same
- // promise.
+ if (this.flushPromise) {
+ if (this.flushAgainPromise) {
+ // this is the 3rd+ time we've called flush() : return the same promise.
return this.flushAgainPromise;
}
- // queue up a flush to occur immediately after the pending one
- // completes.
+ // queue up a flush to occur immediately after the pending one completes.
this.flushAgainPromise = this.flushPromise.then(() => {
+ // clear this.flushAgainPromise
+ this.flushAgainPromise = null;
return this.flush();
});
return this.flushAgainPromise;
@@ -232,12 +234,16 @@ class IndexedDBLogStore {
}
const lines = this.logger.flush();
if (lines.length === 0) {
+ // clear this.flushPromise
+ this.flushPromise = null;
resolve();
return;
}
const txn = this.db.transaction(["logs", "logslastmod"], "readwrite");
const objStore = txn.objectStore("logs");
txn.oncomplete = (event) => {
+ // clear this.flushPromise
+ this.flushPromise = null;
resolve();
};
txn.onerror = (event) => {
diff --git a/src/utils/promise.js b/src/utils/promise.js
index f7a2e7c3e7..e6e6ccb5c8 100644
--- a/src/utils/promise.js
+++ b/src/utils/promise.js
@@ -47,3 +47,20 @@ export function defer(): {resolve: () => {}, reject: () => {}, promise: Promise}
return {resolve, reject, promise};
}
+
+// Promise.allSettled polyfill until browser support is stable in Firefox
+export function allSettled(promises: Promise[]): {status: string, value?: any, reason?: any}[] {
+ if (Promise.allSettled) {
+ return Promise.allSettled(promises);
+ }
+
+ return Promise.all(promises.map((promise) => {
+ return promise.then(value => ({
+ status: "fulfilled",
+ value,
+ })).catch(reason => ({
+ status: "rejected",
+ reason,
+ }));
+ }));
+}