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, + })); + })); +}