Skip to content

Commit 7f98859

Browse files
committed
Stop future errors when encountering an async error
1 parent 4df5bc3 commit 7f98859

File tree

3 files changed

+59
-3
lines changed

3 files changed

+59
-3
lines changed

src/core/friendly_errors/fes_core.js

Lines changed: 43 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,10 @@ function fesCore(p5, fn){
4040
// to enable or disable styling (color, font-size, etc. ) for fes messages
4141
const ENABLE_FES_STYLING = false;
4242

43+
// Used for internally thrown errors that should not get wrapped by another
44+
// friendly error handler
45+
class FESError extends Error {};
46+
4347
if (typeof IS_MINIFIED !== 'undefined') {
4448
p5._friendlyError =
4549
p5._checkForUserDefinedFunctions =
@@ -192,6 +196,26 @@ function fesCore(p5, fn){
192196
log(prefixedMsg);
193197
}
194198
};
199+
200+
/**
201+
* Throws an error with helpful p5 context. Similar to _report, but
202+
* this will stop other code execution to prevent downstream errors
203+
* from being logged.
204+
*
205+
* @method _error
206+
* @private
207+
* @param context p5 instance the error is from
208+
* @param {String} message Message to be printed
209+
* @param {String} [func] Name of function
210+
*/
211+
p5._error = (context, message, func) => {
212+
p5._report(message, func);
213+
context.hitCriticalError = true;
214+
// Throw an error to stop the current function (e.g. setup or draw) from
215+
// running more code
216+
throw new FESError('Stopping sketch to prevent more errors');
217+
};
218+
195219
/**
196220
* This is a generic method that can be called from anywhere in the p5
197221
* library to alert users to a common error.
@@ -274,6 +298,19 @@ function fesCore(p5, fn){
274298
return cur[l2];
275299
};
276300

301+
/**
302+
* Whether or not p5.js is running in an environment where `preload` will be
303+
* run before `setup`.
304+
*
305+
* This will return false for default builds >= 2.0, but backwards compatibility
306+
* addons may set this to true.
307+
*
308+
* @private
309+
*/
310+
p5.isPreloadSupported = function() {
311+
return false;
312+
}
313+
277314
/**
278315
* Checks capitalization for user defined functions.
279316
*
@@ -294,8 +331,8 @@ function fesCore(p5, fn){
294331
context = instanceMode ? context : window;
295332
const fnNames = entryPoints;
296333

297-
if (context.preload) {
298-
p5._friendlyError(translator('fes.preloadDisabled'), 'preload');
334+
if (context.preload && !p5.isPreloadSupported()) {
335+
p5._error(context, translator('fes.preloadDisabled'));
299336
}
300337

301338
const fxns = {};
@@ -633,6 +670,10 @@ function fesCore(p5, fn){
633670
*/
634671
const fesErrorMonitor = e => {
635672
if (p5.disableFriendlyErrors) return;
673+
674+
// Don't try to handle an error intentionally emitted by FES to halt execution
675+
if (e && (e instanceof FESError || e.reason instanceof FESError)) return;
676+
636677
// Try to get the error object from e
637678
let error;
638679
if (e instanceof Error) {

src/core/friendly_errors/param_validator.js

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -354,6 +354,7 @@ function validateParams(p5, fn, lifecycles) {
354354
*/
355355
fn.friendlyParamError = function (zodErrorObj, func, args) {
356356
let message = '🌸 p5.js says: ';
357+
let isVersionError = false;
357358
// The `zodErrorObj` might contain multiple errors of equal importance
358359
// (after scoring the schema closeness in `findClosestSchema`). Here, we
359360
// always print the first error so that user can work through the errors
@@ -401,6 +402,7 @@ function validateParams(p5, fn, lifecycles) {
401402
if (error.path?.length > 0 && args[error.path[0]] instanceof Promise) {
402403
message += 'Did you mean to put `await` before a loading function? ' +
403404
'An unexpected Promise was found. ';
405+
isVersionError = true;
404406
}
405407

406408
const expectedTypesStr = Array.from(expectedTypes).join(' or ');
@@ -455,7 +457,11 @@ function validateParams(p5, fn, lifecycles) {
455457
message += ` For more information, see ${documentationLink}.`;
456458
}
457459

458-
console.log(message);
460+
if (isVersionError) {
461+
p5._error(this, message);
462+
} else {
463+
console.log(message);
464+
}
459465
return message;
460466
}
461467

src/core/main.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ class p5 {
5151
// PRIVATE p5 PROPERTIES AND METHODS
5252
//////////////////////////////////////////////
5353

54+
this.hitCriticalError = false;
5455
this._setupDone = false;
5556
this._userNode = node;
5657
this._curElement = null;
@@ -124,6 +125,9 @@ class p5 {
124125
// assume "global" mode and make everything global (i.e. on the window)
125126
if (!sketch) {
126127
this._isGlobal = true;
128+
if (window.hitCriticalError) {
129+
return;
130+
}
127131
p5.instance = this;
128132

129133
// Loop through methods on the prototype and attach them to the window
@@ -199,6 +203,7 @@ class p5 {
199203
}
200204

201205
async #_start() {
206+
if (this.hitCriticalError) return;
202207
// Find node if id given
203208
if (this._userNode) {
204209
if (typeof this._userNode === 'string') {
@@ -207,6 +212,7 @@ class p5 {
207212
}
208213

209214
await this.#_setup();
215+
if (this.hitCriticalError) return;
210216
if (!this._recording) {
211217
this._draw();
212218
}
@@ -215,6 +221,7 @@ class p5 {
215221
async #_setup() {
216222
// Run `presetup` hooks
217223
await this._runLifecycleHook('presetup');
224+
if (this.hitCriticalError) return;
218225

219226
// Always create a default canvas.
220227
// Later on if the user calls createCanvas, this default one
@@ -232,6 +239,7 @@ class p5 {
232239
if (typeof context.setup === 'function') {
233240
await context.setup();
234241
}
242+
if (this.hitCriticalError) return;
235243

236244
// unhide any hidden canvases that were created
237245
const canvases = document.getElementsByTagName('canvas');
@@ -268,6 +276,7 @@ class p5 {
268276
// current frame finish awaiting. The same goes for lifecycle hooks 'predraw'
269277
// and 'postdraw'.
270278
async _draw(requestAnimationFrameTimestamp) {
279+
if (this.hitCriticalError) return;
271280
const now = requestAnimationFrameTimestamp || window.performance.now();
272281
const timeSinceLastFrame = now - this._lastTargetFrameTime;
273282
const targetTimeBetweenFrames = 1000 / this._targetFrameRate;

0 commit comments

Comments
 (0)