From 217dfc3eed0c5f018a986ddc4f1c05a1a31b5960 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 12 Nov 2019 11:40:38 +0000 Subject: [PATCH 1/7] Replace all trivial Promise.defer usages with regular Promises (cherry picked from commit 44401d73b44b6de4daeb91613456d2760d50456d) --- src/ContentMessages.js | 174 +++++++++--------- src/Lifecycle.js | 8 +- src/components/views/rooms/Autocomplete.js | 29 ++- .../views/settings/ChangePassword.js | 9 +- src/rageshake/submit-rageshake.js | 40 ++-- 5 files changed, 122 insertions(+), 138 deletions(-) diff --git a/src/ContentMessages.js b/src/ContentMessages.js index 2d58622db8..dab8de2465 100644 --- a/src/ContentMessages.js +++ b/src/ContentMessages.js @@ -59,40 +59,38 @@ export class UploadCanceledError extends Error {} * and a thumbnail key. */ function createThumbnail(element, inputWidth, inputHeight, mimeType) { - const deferred = Promise.defer(); + return new Promise((resolve) => { + let targetWidth = inputWidth; + let targetHeight = inputHeight; + if (targetHeight > MAX_HEIGHT) { + targetWidth = Math.floor(targetWidth * (MAX_HEIGHT / targetHeight)); + targetHeight = MAX_HEIGHT; + } + if (targetWidth > MAX_WIDTH) { + targetHeight = Math.floor(targetHeight * (MAX_WIDTH / targetWidth)); + targetWidth = MAX_WIDTH; + } - let targetWidth = inputWidth; - let targetHeight = inputHeight; - if (targetHeight > MAX_HEIGHT) { - targetWidth = Math.floor(targetWidth * (MAX_HEIGHT / targetHeight)); - targetHeight = MAX_HEIGHT; - } - if (targetWidth > MAX_WIDTH) { - targetHeight = Math.floor(targetHeight * (MAX_WIDTH / targetWidth)); - targetWidth = MAX_WIDTH; - } - - const canvas = document.createElement("canvas"); - canvas.width = targetWidth; - canvas.height = targetHeight; - canvas.getContext("2d").drawImage(element, 0, 0, targetWidth, targetHeight); - canvas.toBlob(function(thumbnail) { - deferred.resolve({ - info: { - thumbnail_info: { - w: targetWidth, - h: targetHeight, - mimetype: thumbnail.type, - size: thumbnail.size, + const canvas = document.createElement("canvas"); + canvas.width = targetWidth; + canvas.height = targetHeight; + canvas.getContext("2d").drawImage(element, 0, 0, targetWidth, targetHeight); + canvas.toBlob(function(thumbnail) { + resolve({ + info: { + thumbnail_info: { + w: targetWidth, + h: targetHeight, + mimetype: thumbnail.type, + size: thumbnail.size, + }, + w: inputWidth, + h: inputHeight, }, - w: inputWidth, - h: inputHeight, - }, - thumbnail: thumbnail, - }); - }, mimeType); - - return deferred.promise; + thumbnail: thumbnail, + }); + }, mimeType); + }); } /** @@ -179,30 +177,29 @@ function infoForImageFile(matrixClient, roomId, imageFile) { * @return {Promise} A promise that resolves with the video image element. */ function loadVideoElement(videoFile) { - const deferred = Promise.defer(); + return new Promise((resolve, reject) => { + // Load the file into an html element + const video = document.createElement("video"); - // Load the file into an html element - const video = document.createElement("video"); + const reader = new FileReader(); - const reader = new FileReader(); - reader.onload = function(e) { - video.src = e.target.result; + reader.onload = function(e) { + video.src = e.target.result; - // Once ready, returns its size - // Wait until we have enough data to thumbnail the first frame. - video.onloadeddata = function() { - deferred.resolve(video); + // Once ready, returns its size + // Wait until we have enough data to thumbnail the first frame. + video.onloadeddata = function() { + resolve(video); + }; + video.onerror = function(e) { + reject(e); + }; }; - video.onerror = function(e) { - deferred.reject(e); + reader.onerror = function(e) { + reject(e); }; - }; - reader.onerror = function(e) { - deferred.reject(e); - }; - reader.readAsDataURL(videoFile); - - return deferred.promise; + reader.readAsDataURL(videoFile); + }); } /** @@ -236,16 +233,16 @@ function infoForVideoFile(matrixClient, roomId, videoFile) { * is read. */ function readFileAsArrayBuffer(file) { - const deferred = Promise.defer(); - const reader = new FileReader(); - reader.onload = function(e) { - deferred.resolve(e.target.result); - }; - reader.onerror = function(e) { - deferred.reject(e); - }; - reader.readAsArrayBuffer(file); - return deferred.promise; + return new Promise((resolve, reject) => { + const reader = new FileReader(); + reader.onload = function(e) { + resolve(e.target.result); + }; + reader.onerror = function(e) { + reject(e); + }; + reader.readAsArrayBuffer(file); + }); } /** @@ -461,33 +458,34 @@ export default class ContentMessages { content.info.mimetype = file.type; } - const def = Promise.defer(); - if (file.type.indexOf('image/') == 0) { - content.msgtype = 'm.image'; - infoForImageFile(matrixClient, roomId, file).then((imageInfo)=>{ - extend(content.info, imageInfo); - def.resolve(); - }, (error)=>{ - console.error(error); + const prom = new Promise((resolve) => { + if (file.type.indexOf('image/') == 0) { + content.msgtype = 'm.image'; + infoForImageFile(matrixClient, roomId, file).then((imageInfo)=>{ + extend(content.info, imageInfo); + resolve(); + }, (error)=>{ + console.error(error); + content.msgtype = 'm.file'; + resolve(); + }); + } else if (file.type.indexOf('audio/') == 0) { + content.msgtype = 'm.audio'; + resolve(); + } else if (file.type.indexOf('video/') == 0) { + content.msgtype = 'm.video'; + infoForVideoFile(matrixClient, roomId, file).then((videoInfo)=>{ + extend(content.info, videoInfo); + resolve(); + }, (error)=>{ + content.msgtype = 'm.file'; + resolve(); + }); + } else { content.msgtype = 'm.file'; - def.resolve(); - }); - } else if (file.type.indexOf('audio/') == 0) { - content.msgtype = 'm.audio'; - def.resolve(); - } else if (file.type.indexOf('video/') == 0) { - content.msgtype = 'm.video'; - infoForVideoFile(matrixClient, roomId, file).then((videoInfo)=>{ - extend(content.info, videoInfo); - def.resolve(); - }, (error)=>{ - content.msgtype = 'm.file'; - def.resolve(); - }); - } else { - content.msgtype = 'm.file'; - def.resolve(); - } + resolve(); + } + }); const upload = { fileName: file.name || 'Attachment', @@ -509,7 +507,7 @@ export default class ContentMessages { dis.dispatch({action: 'upload_progress', upload: upload}); } - return def.promise.then(function() { + return prom.then(function() { // XXX: upload.promise must be the promise that // is returned by uploadFile as it has an abort() // method hacked onto it. diff --git a/src/Lifecycle.js b/src/Lifecycle.js index 13f3abccb1..53a9b7a998 100644 --- a/src/Lifecycle.js +++ b/src/Lifecycle.js @@ -312,18 +312,14 @@ async function _restoreFromLocalStorage(opts) { function _handleLoadSessionFailure(e) { console.error("Unable to load session", e); - const def = Promise.defer(); const SessionRestoreErrorDialog = sdk.getComponent('views.dialogs.SessionRestoreErrorDialog'); - Modal.createTrackedDialog('Session Restore Error', '', SessionRestoreErrorDialog, { + const modal = Modal.createTrackedDialog('Session Restore Error', '', SessionRestoreErrorDialog, { error: e.message, - onFinished: (success) => { - def.resolve(success); - }, }); - return def.promise.then((success) => { + return modal.finished.then(([success]) => { if (success) { // user clicked continue. _clearStorage(); diff --git a/src/components/views/rooms/Autocomplete.js b/src/components/views/rooms/Autocomplete.js index ad5fa198a3..d4b51081f4 100644 --- a/src/components/views/rooms/Autocomplete.js +++ b/src/components/views/rooms/Autocomplete.js @@ -26,6 +26,7 @@ import { Room } from 'matrix-js-sdk'; import SettingsStore from "../../../settings/SettingsStore"; import Autocompleter from '../../../autocomplete/Autocompleter'; +import {sleep} from "../../../utils/promise"; const COMPOSER_SELECTED = 0; @@ -105,13 +106,11 @@ export default class Autocomplete extends React.Component { autocompleteDelay = 0; } - const deferred = Promise.defer(); - this.debounceCompletionsRequest = setTimeout(() => { - this.processQuery(query, selection).then(() => { - deferred.resolve(); - }); - }, autocompleteDelay); - return deferred.promise; + return new Promise((resolve) => { + this.debounceCompletionsRequest = setTimeout(() => { + resolve(this.processQuery(query, selection)); + }, autocompleteDelay); + }); } processQuery(query, selection) { @@ -197,16 +196,16 @@ export default class Autocomplete extends React.Component { } forceComplete() { - const done = Promise.defer(); - this.setState({ - forceComplete: true, - hide: false, - }, () => { - this.complete(this.props.query, this.props.selection).then(() => { - done.resolve(this.countCompletions()); + return new Promise((resolve) => { + this.setState({ + forceComplete: true, + hide: false, + }, () => { + this.complete(this.props.query, this.props.selection).then(() => { + resolve(this.countCompletions()); + }); }); }); - return done.promise; } onCompletionClicked(selectionOffset: number): boolean { diff --git a/src/components/views/settings/ChangePassword.js b/src/components/views/settings/ChangePassword.js index a086efaa6d..91292b19f9 100644 --- a/src/components/views/settings/ChangePassword.js +++ b/src/components/views/settings/ChangePassword.js @@ -178,17 +178,12 @@ module.exports = createReactClass({ }, _optionallySetEmail: function() { - const deferred = Promise.defer(); // Ask for an email otherwise the user has no way to reset their password const SetEmailDialog = sdk.getComponent("dialogs.SetEmailDialog"); - Modal.createTrackedDialog('Do you want to set an email address?', '', SetEmailDialog, { + const modal = Modal.createTrackedDialog('Do you want to set an email address?', '', SetEmailDialog, { title: _t('Do you want to set an email address?'), - onFinished: (confirmed) => { - // ignore confirmed, setting an email is optional - deferred.resolve(confirmed); - }, }); - return deferred.promise; + return modal.finished.then(([confirmed]) => confirmed); }, _onExportE2eKeysClicked: function() { diff --git a/src/rageshake/submit-rageshake.js b/src/rageshake/submit-rageshake.js index 99c412a6ab..e772912e48 100644 --- a/src/rageshake/submit-rageshake.js +++ b/src/rageshake/submit-rageshake.js @@ -105,26 +105,22 @@ export default async function sendBugReport(bugReportEndpoint, opts) { } function _submitReport(endpoint, body, progressCallback) { - const deferred = Promise.defer(); - - const req = new XMLHttpRequest(); - req.open("POST", endpoint); - req.timeout = 5 * 60 * 1000; - req.onreadystatechange = function() { - if (req.readyState === XMLHttpRequest.LOADING) { - progressCallback(_t("Waiting for response from server")); - } else if (req.readyState === XMLHttpRequest.DONE) { - on_done(); - } - }; - req.send(body); - return deferred.promise; - - function on_done() { - if (req.status < 200 || req.status >= 400) { - deferred.reject(new Error(`HTTP ${req.status}`)); - return; - } - deferred.resolve(); - } + return new Promise((resolve, reject) => { + const req = new XMLHttpRequest(); + req.open("POST", endpoint); + req.timeout = 5 * 60 * 1000; + req.onreadystatechange = function() { + if (req.readyState === XMLHttpRequest.LOADING) { + progressCallback(_t("Waiting for response from server")); + } else if (req.readyState === XMLHttpRequest.DONE) { + // on done + if (req.status < 200 || req.status >= 400) { + reject(new Error(`HTTP ${req.status}`)); + return; + } + resolve(); + } + }; + req.send(body); + }); } From 2ea239d1923170ef9d226eeae99dcfc12432dc30 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 12 Nov 2019 11:45:28 +0000 Subject: [PATCH 2/7] Replace rest of defer usages using small shim. Add homebrew promise utils (cherry picked from commit 6850c147393ba7be8b98d97dfc8d7244fa503461) --- src/Modal.js | 3 +- src/components/structures/MatrixChat.js | 5 +-- src/utils/MultiInviter.js | 3 +- src/utils/promise.js | 46 +++++++++++++++++++++++++ 4 files changed, 53 insertions(+), 4 deletions(-) create mode 100644 src/utils/promise.js diff --git a/src/Modal.js b/src/Modal.js index 26c9da8bbb..cb19731f01 100644 --- a/src/Modal.js +++ b/src/Modal.js @@ -24,6 +24,7 @@ import sdk from './index'; import dis from './dispatcher'; import { _t } from './languageHandler'; import Promise from "bluebird"; +import {defer} from "./utils/promise"; const DIALOG_CONTAINER_ID = "mx_Dialog_Container"; const STATIC_DIALOG_CONTAINER_ID = "mx_Dialog_StaticContainer"; @@ -202,7 +203,7 @@ class ModalManager { } _getCloseFn(modal, props) { - const deferred = Promise.defer(); + const deferred = defer(); return [(...args) => { deferred.resolve(args); if (props && props.onFinished) props.onFinished.apply(null, args); diff --git a/src/components/structures/MatrixChat.js b/src/components/structures/MatrixChat.js index da67416400..d12eba88f7 100644 --- a/src/components/structures/MatrixChat.js +++ b/src/components/structures/MatrixChat.js @@ -60,6 +60,7 @@ import AutoDiscoveryUtils from "../../utils/AutoDiscoveryUtils"; import DMRoomMap from '../../utils/DMRoomMap'; import { countRoomsWithNotif } from '../../RoomNotifs'; import { setTheme } from "../../theme"; +import {defer} from "../../utils/promise"; // Disable warnings for now: we use deprecated bluebird functions // and need to migrate, but they spam the console with warnings. @@ -236,7 +237,7 @@ export default createReactClass({ // Used by _viewRoom before getting state from sync this.firstSyncComplete = false; - this.firstSyncPromise = Promise.defer(); + this.firstSyncPromise = defer(); if (this.props.config.sync_timeline_limit) { MatrixClientPeg.opts.initialSyncLimit = this.props.config.sync_timeline_limit; @@ -1261,7 +1262,7 @@ export default createReactClass({ // since we're about to start the client and therefore about // to do the first sync this.firstSyncComplete = false; - this.firstSyncPromise = Promise.defer(); + this.firstSyncPromise = defer(); const cli = MatrixClientPeg.get(); const IncomingSasDialog = sdk.getComponent('views.dialogs.IncomingSasDialog'); diff --git a/src/utils/MultiInviter.js b/src/utils/MultiInviter.js index e8995b46d7..de5c2e7610 100644 --- a/src/utils/MultiInviter.js +++ b/src/utils/MultiInviter.js @@ -24,6 +24,7 @@ import {_t} from "../languageHandler"; import sdk from "../index"; import Modal from "../Modal"; import SettingsStore from "../settings/SettingsStore"; +import {defer} from "./promise"; /** * Invites multiple addresses to a room or group, handling rate limiting from the server @@ -71,7 +72,7 @@ export default class MultiInviter { }; } } - this.deferred = Promise.defer(); + this.deferred = defer(); this._inviteMore(0); return this.deferred.promise; diff --git a/src/utils/promise.js b/src/utils/promise.js new file mode 100644 index 0000000000..dd10f7fdd7 --- /dev/null +++ b/src/utils/promise.js @@ -0,0 +1,46 @@ +/* +Copyright 2019 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// @flow + +// Returns a promise which resolves with a given value after the given number of ms +export const sleep = (ms: number, value: any): Promise => new Promise((resolve => { setTimeout(resolve, ms, value); })); + +// Returns a promise which resolves when the input promise resolves with its value +// or when the timeout of ms is reached with the value of given timeoutValue +export async function timeout(promise: Promise, timeoutValue: any, ms: number): Promise { + const timeoutPromise = new Promise((resolve) => { + const timeoutId = setTimeout(resolve, ms, timeoutValue); + promise.then(() => { + clearTimeout(timeoutId); + }); + }); + + return Promise.race([promise, timeoutPromise]); +} + +// Returns a Deferred +export function defer(): {resolve: () => {}, reject: () => {}, promise: Promise} { + let resolve; + let reject; + + const promise = new Promise((_resolve, _reject) => { + resolve = _resolve; + reject = _reject; + }); + + return {resolve, reject, promise}; +} From 2b34cf4362ab03b0409da6086199ef2eab54dee7 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 12 Nov 2019 11:46:58 +0000 Subject: [PATCH 3/7] Replace Promise.delay with promise utils sleep (cherry picked from commit 0a21957b2cac0cc2d0169f428187bc2e468251a9) --- src/components/structures/GroupView.js | 9 +++++---- .../views/context_menus/RoomTileContextMenu.js | 7 ++++--- src/components/views/dialogs/AddressPickerDialog.js | 3 ++- .../views/settings/tabs/user/SecurityUserSettingsTab.js | 3 ++- 4 files changed, 13 insertions(+), 9 deletions(-) diff --git a/src/components/structures/GroupView.js b/src/components/structures/GroupView.js index 4d8f47003c..4056557a7c 100644 --- a/src/components/structures/GroupView.js +++ b/src/components/structures/GroupView.js @@ -38,6 +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"; const LONG_DESC_PLACEHOLDER = _td( `