mirror of
https://github.com/pybricks/pybricks-code.git
synced 2026-09-12 09:36:27 +00:00
src/utils: add ensureError() function
Typescript v4.4 no longer assumes that the error in catch is an Error object [1]. This adds a helper function to ensure that caught errors at least match the Error interface. If not, it creates a new Error object with the value as the message. [1]: https://devblogs.microsoft.com/typescript/announcing-typescript-4-4-beta/#using-unknown-in-catch-variables
This commit is contained in:
committed by
David Lechner
parent
79db359398
commit
e7d44ea9a9
@@ -12,7 +12,7 @@ import {
|
||||
takeEvery,
|
||||
} from 'typed-redux-saga/macro';
|
||||
import { Action } from '../actions';
|
||||
import { hex } from '../utils';
|
||||
import { ensureError, hex } from '../utils';
|
||||
import {
|
||||
BlePybricksServiceActionType,
|
||||
BlePybricksServiceCommandAction,
|
||||
@@ -101,7 +101,7 @@ function* decodeResponse(action: BlePybricksServiceDidNotifyEventAction): Genera
|
||||
);
|
||||
}
|
||||
} catch (err) {
|
||||
yield* put(eventProtocolError(err));
|
||||
yield* put(eventProtocolError(ensureError(err)));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+28
-18
@@ -48,7 +48,7 @@ import {
|
||||
} from '../ble/actions';
|
||||
import { BleConnectionState } from '../ble/reducers';
|
||||
import { RootState } from '../reducers';
|
||||
import { hex } from '../utils';
|
||||
import { ensureError, hex } from '../utils';
|
||||
import {
|
||||
BleUartActionType,
|
||||
BleUartWriteAction,
|
||||
@@ -83,7 +83,7 @@ function* writePybricksCommand(
|
||||
yield* call(() => char.writeValueWithoutResponse(action.value.buffer));
|
||||
yield* put(didWriteCommand(action.id));
|
||||
} catch (err) {
|
||||
yield* put(didFailToWriteCommand(action.id, err));
|
||||
yield* put(didFailToWriteCommand(action.id, ensureError(err)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -99,7 +99,7 @@ function* writeUart(
|
||||
yield* call(() => char.writeValueWithoutResponse(action.value.buffer));
|
||||
yield* put(didWriteUart(action.id));
|
||||
} catch (err) {
|
||||
yield* put(didFailToWriteUart(action.id, err));
|
||||
yield* put(didFailToWriteUart(action.id, ensureError(err)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -132,7 +132,9 @@ function* connect(_action: BleDeviceConnectAction): Generator {
|
||||
// this can happen if the use cancels the dialog
|
||||
yield* put(didFailToConnect({ reason: Reason.Canceled }));
|
||||
} else {
|
||||
yield* put(didFailToConnect({ reason: Reason.Unknown, err }));
|
||||
yield* put(
|
||||
didFailToConnect({ reason: Reason.Unknown, err: ensureError(err) }),
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -154,7 +156,7 @@ function* connect(_action: BleDeviceConnectAction): Generator {
|
||||
server = yield* call([device.gatt, 'connect']);
|
||||
} catch (err) {
|
||||
disconnectChannel.close();
|
||||
yield* put(didFailToConnect({ reason: Reason.Unknown, err }));
|
||||
yield* put(didFailToConnect({ reason: Reason.Unknown, err: ensureError(err) }));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -172,7 +174,9 @@ function* connect(_action: BleDeviceConnectAction): Generator {
|
||||
if (err instanceof DOMException && err.code === DOMException.NOT_FOUND_ERR) {
|
||||
yield* put(didFailToConnect({ reason: Reason.NoDeviceInfoService }));
|
||||
} else {
|
||||
yield* put(didFailToConnect({ reason: Reason.Unknown, err }));
|
||||
yield* put(
|
||||
didFailToConnect({ reason: Reason.Unknown, err: ensureError(err) }),
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -186,7 +190,7 @@ function* connect(_action: BleDeviceConnectAction): Generator {
|
||||
} catch (err) {
|
||||
server.disconnect();
|
||||
yield* takeMaybe(disconnectChannel);
|
||||
yield* put(didFailToConnect({ reason: Reason.Unknown, err }));
|
||||
yield* put(didFailToConnect({ reason: Reason.Unknown, err: ensureError(err) }));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -198,7 +202,7 @@ function* connect(_action: BleDeviceConnectAction): Generator {
|
||||
} catch (err) {
|
||||
server.disconnect();
|
||||
yield* takeMaybe(disconnectChannel);
|
||||
yield* put(didFailToConnect({ reason: Reason.Unknown, err }));
|
||||
yield* put(didFailToConnect({ reason: Reason.Unknown, err: ensureError(err) }));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -214,7 +218,7 @@ function* connect(_action: BleDeviceConnectAction): Generator {
|
||||
} catch (err) {
|
||||
server.disconnect();
|
||||
yield* takeMaybe(disconnectChannel);
|
||||
yield* put(didFailToConnect({ reason: Reason.Unknown, err }));
|
||||
yield* put(didFailToConnect({ reason: Reason.Unknown, err: ensureError(err) }));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -226,7 +230,7 @@ function* connect(_action: BleDeviceConnectAction): Generator {
|
||||
} catch (err) {
|
||||
server.disconnect();
|
||||
yield* takeMaybe(disconnectChannel);
|
||||
yield* put(didFailToConnect({ reason: Reason.Unknown, err }));
|
||||
yield* put(didFailToConnect({ reason: Reason.Unknown, err: ensureError(err) }));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -249,7 +253,9 @@ function* connect(_action: BleDeviceConnectAction): Generator {
|
||||
} catch (err) {
|
||||
server.disconnect();
|
||||
yield* takeMaybe(disconnectChannel);
|
||||
yield* put(didFailToConnect({ reason: Reason.Unknown, err }));
|
||||
yield* put(
|
||||
didFailToConnect({ reason: Reason.Unknown, err: ensureError(err) }),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -273,7 +279,9 @@ function* connect(_action: BleDeviceConnectAction): Generator {
|
||||
if (err instanceof DOMException && err.code === DOMException.NOT_FOUND_ERR) {
|
||||
yield* put(didFailToConnect({ reason: Reason.NoPybricksService }));
|
||||
} else {
|
||||
yield* put(didFailToConnect({ reason: Reason.Unknown, err }));
|
||||
yield* put(
|
||||
didFailToConnect({ reason: Reason.Unknown, err: ensureError(err) }),
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -287,7 +295,7 @@ function* connect(_action: BleDeviceConnectAction): Generator {
|
||||
} catch (err) {
|
||||
server.disconnect();
|
||||
yield* takeMaybe(disconnectChannel);
|
||||
yield* put(didFailToConnect({ reason: Reason.Unknown, err }));
|
||||
yield* put(didFailToConnect({ reason: Reason.Unknown, err: ensureError(err) }));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -326,7 +334,7 @@ function* connect(_action: BleDeviceConnectAction): Generator {
|
||||
pybricksControlChannel.close();
|
||||
server.disconnect();
|
||||
yield* takeMaybe(disconnectChannel);
|
||||
yield* put(didFailToConnect({ reason: Reason.Unknown, err }));
|
||||
yield* put(didFailToConnect({ reason: Reason.Unknown, err: ensureError(err) }));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -349,7 +357,9 @@ function* connect(_action: BleDeviceConnectAction): Generator {
|
||||
if (err instanceof DOMException && err.code === DOMException.NOT_FOUND_ERR) {
|
||||
yield* put(didFailToConnect({ reason: Reason.NoPybricksService }));
|
||||
} else {
|
||||
yield* put(didFailToConnect({ reason: Reason.Unknown, err }));
|
||||
yield* put(
|
||||
didFailToConnect({ reason: Reason.Unknown, err: ensureError(err) }),
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -362,7 +372,7 @@ function* connect(_action: BleDeviceConnectAction): Generator {
|
||||
pybricksControlChannel.close();
|
||||
server.disconnect();
|
||||
yield* takeMaybe(disconnectChannel);
|
||||
yield* put(didFailToConnect({ reason: Reason.Unknown, err }));
|
||||
yield* put(didFailToConnect({ reason: Reason.Unknown, err: ensureError(err) }));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -374,7 +384,7 @@ function* connect(_action: BleDeviceConnectAction): Generator {
|
||||
pybricksControlChannel.close();
|
||||
server.disconnect();
|
||||
yield* takeMaybe(disconnectChannel);
|
||||
yield* put(didFailToConnect({ reason: Reason.Unknown, err }));
|
||||
yield* put(didFailToConnect({ reason: Reason.Unknown, err: ensureError(err) }));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -406,7 +416,7 @@ function* connect(_action: BleDeviceConnectAction): Generator {
|
||||
pybricksControlChannel.close();
|
||||
server.disconnect();
|
||||
yield* takeMaybe(disconnectChannel);
|
||||
yield* put(didFailToConnect({ reason: Reason.Unknown, err }));
|
||||
yield* put(didFailToConnect({ reason: Reason.Unknown, err: ensureError(err) }));
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -53,7 +53,7 @@ import {
|
||||
compile,
|
||||
} from '../mpy/actions';
|
||||
import { RootState } from '../reducers';
|
||||
import { defined, hex, maybe } from '../utils';
|
||||
import { defined, ensureError, hex, maybe } from '../utils';
|
||||
import { fmod, sumComplement32 } from '../utils/math';
|
||||
import { isAndroid } from '../utils/os';
|
||||
import {
|
||||
@@ -487,7 +487,7 @@ function* flashFirmware(action: FlashFirmwareFlashAction): Generator {
|
||||
|
||||
yield* put(didFinish());
|
||||
} catch (err) {
|
||||
yield* put(didFailToFinish(FailToFinishReasonType.Unknown, err));
|
||||
yield* put(didFailToFinish(FailToFinishReasonType.Unknown, ensureError(err)));
|
||||
yield* disconnectAndCancel();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
import { END, eventChannel } from 'redux-saga';
|
||||
import { call, cancel, put, spawn, takeEvery, takeMaybe } from 'typed-redux-saga/macro';
|
||||
import { ensureError } from '../utils';
|
||||
import {
|
||||
BootloaderConnectionAction,
|
||||
BootloaderConnectionActionType,
|
||||
@@ -35,7 +36,7 @@ function* write(
|
||||
}
|
||||
yield* put(didSend());
|
||||
} catch (err) {
|
||||
yield* put(didFailToSend(err));
|
||||
yield* put(didFailToSend(ensureError(err)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,7 +65,7 @@ function* connect(_action: BootloaderConnectionAction): Generator {
|
||||
// this can happen if the use cancels the dialog
|
||||
yield* put(didFailToConnect(Reason.Canceled));
|
||||
} else {
|
||||
yield* put(didFailToConnect(Reason.Unknown, err));
|
||||
yield* put(didFailToConnect(Reason.Unknown, ensureError(err)));
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -88,7 +89,7 @@ function* connect(_action: BootloaderConnectionAction): Generator {
|
||||
server = yield* call([device.gatt, 'connect']);
|
||||
} catch (err) {
|
||||
disconnectChannel.close();
|
||||
yield* put(didFailToConnect(Reason.Unknown, err));
|
||||
yield* put(didFailToConnect(Reason.Unknown, ensureError(err)));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -103,7 +104,7 @@ function* connect(_action: BootloaderConnectionAction): Generator {
|
||||
// https://chromium-review.googlesource.com/c/chromium/src/+/2214098
|
||||
yield* put(didFailToConnect(Reason.GattServiceNotFound));
|
||||
} else {
|
||||
yield* put(didFailToConnect(Reason.Unknown, err));
|
||||
yield* put(didFailToConnect(Reason.Unknown, ensureError(err)));
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -117,7 +118,7 @@ function* connect(_action: BootloaderConnectionAction): Generator {
|
||||
} catch (err) {
|
||||
server.disconnect();
|
||||
yield* takeMaybe(disconnectChannel);
|
||||
yield* put(didFailToConnect(Reason.Unknown, err));
|
||||
yield* put(didFailToConnect(Reason.Unknown, ensureError(err)));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -147,7 +148,7 @@ function* connect(_action: BootloaderConnectionAction): Generator {
|
||||
notificationChannel.close();
|
||||
server.disconnect();
|
||||
yield* takeMaybe(disconnectChannel);
|
||||
yield* put(didFailToConnect(Reason.Unknown, err));
|
||||
yield* put(didFailToConnect(Reason.Unknown, ensureError(err)));
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
takeEvery,
|
||||
} from 'typed-redux-saga/macro';
|
||||
import { Action } from '../actions';
|
||||
import { hex } from '../utils';
|
||||
import { ensureError, hex } from '../utils';
|
||||
import { isWindows } from '../utils/os';
|
||||
import {
|
||||
BootloaderConnectionActionType,
|
||||
@@ -172,7 +172,7 @@ function* decodeResponse(action: BootloaderConnectionDidReceiveAction): Generato
|
||||
);
|
||||
}
|
||||
} catch (err) {
|
||||
yield* put(didError(err));
|
||||
yield* put(didError(ensureError(err)));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ import { EventChannel, eventChannel } from 'redux-saga';
|
||||
import { call, fork, put, select, take, takeEvery } from 'typed-redux-saga/macro';
|
||||
import { AppActionType } from '../app/actions';
|
||||
import { RootState } from '../reducers';
|
||||
import { ensureError } from '../utils';
|
||||
import {
|
||||
SettingsActionType,
|
||||
SettingsSetBooleanAction,
|
||||
@@ -80,7 +81,7 @@ function* storeSetting(action: SettingsSetBooleanAction): Generator {
|
||||
try {
|
||||
localStorage.setItem(key, newValue);
|
||||
} catch (err) {
|
||||
yield* put(didFailToSetBoolean(action.id, err));
|
||||
yield* put(didFailToSetBoolean(action.id, ensureError(err)));
|
||||
}
|
||||
|
||||
// storage event is only raised when a value is changed externally, so we
|
||||
|
||||
+11
-1
@@ -1,7 +1,7 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright (c) 2020 The Pybricks Authors
|
||||
|
||||
import { assert, defined, hex, maybe } from '.';
|
||||
import { assert, defined, ensureError, hex, maybe } from '.';
|
||||
|
||||
test('assert', () => {
|
||||
const assertTrue = jest.fn(() => assert(true, 'should not throw'));
|
||||
@@ -34,3 +34,13 @@ test('hex', () => {
|
||||
expect(hex(1, 4)).toBe('0x0001');
|
||||
expect(hex(2, 8)).toBe('0x00000002');
|
||||
});
|
||||
|
||||
test('ensureError', () => {
|
||||
const err = new Error('test error');
|
||||
expect(ensureError(err)).toBe(err);
|
||||
|
||||
const stringToErrorMessage = 'not an Error';
|
||||
const stringToError = expect(ensureError(stringToErrorMessage));
|
||||
stringToError.toHaveProperty('name', 'Error');
|
||||
stringToError.toHaveProperty('message', stringToErrorMessage);
|
||||
});
|
||||
|
||||
+24
-2
@@ -1,5 +1,5 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright (c) 2020 The Pybricks Authors
|
||||
// Copyright (c) 2020-2021 The Pybricks Authors
|
||||
|
||||
/**
|
||||
* Asserts that an assumption is true. This is used to detect programmer errors
|
||||
@@ -29,7 +29,7 @@ export async function maybe<T>(promise: Promise<T>): Promise<Maybe<T>> {
|
||||
try {
|
||||
return [await promise];
|
||||
} catch (err) {
|
||||
return [undefined, err];
|
||||
return [undefined, ensureError(err)];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,3 +41,25 @@ export async function maybe<T>(promise: Promise<T>): Promise<Maybe<T>> {
|
||||
export function hex(n: number, pad: number): string {
|
||||
return `0x${n.toString(16).padStart(pad, '0')}`;
|
||||
}
|
||||
|
||||
function isError(err: unknown): err is Error {
|
||||
const maybeError = err as Error;
|
||||
|
||||
return (
|
||||
maybeError !== undefined &&
|
||||
typeof maybeError.name === 'string' &&
|
||||
typeof maybeError.message === 'string'
|
||||
);
|
||||
}
|
||||
|
||||
export function ensureError(err: unknown): Error {
|
||||
if (isError(err)) {
|
||||
return err;
|
||||
}
|
||||
|
||||
if (typeof err === 'string') {
|
||||
return new Error(err);
|
||||
}
|
||||
|
||||
return Error(String(err));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user