I have this issue in my working code (Scheme Interpreter in JavaScritp called LIPS) I have a Promise constructor where I throw and resolve at the same. But the code is a bit complex and there is unpromise function that handles promises or simple values in a lot of places. The code may or may not be async.
And in that code, I have basically code like this:
function x() {
return new Promise(resolve => {
resolve();
throw new FakeError("<ZONK>");
});
}
I’m almost sure the code is pretty much the same, I have resolved and thrown in the Promise constructor. The FakeError was added inside the try..catch macro to break the evaluation when there is an exception in the user code:
(let ((result #f))
(try
(begin
(set! result 1)
(throw 'ZONK)
(set! result 2))
(catch (e)
(set! result 3)))
(t.is result 3))
This is a basic test that I use to test if it works. And It does:
If I use:
(try (throw 'ZONK) (catch (e) 10))
it evaluates to 10 so it’s working perfectly fine.
This is part of my real code:
This is code that triggers a macro, invoke is call to the function attached to the macro:
function evaluate_macro(macro, code, eval_args) {
function finalize(result) {
if (result instanceof Pair) {
result.markCycles();
return result;
}
return quote(result);
}
try {
var value = macro.invoke(code, eval_args);
return unpromise(resolve_promises(value), function ret(value) {
if (!value || value && value[__data__] || self_evaluated(value)) {
return value;
} else {
return unpromise(evaluate(value, eval_args), finalize);
}
}, error => {
if (!(error instanceof IgnoreException)) {
throw error;
} else {
console.log('<<<ERROR>>>');
}
});
} catch (error) {
if (!(error instanceof IgnoreException)) {
throw error;
} else {
console.log('<<<EXCEPTION>>>');
}
}
}
and here is ‘try’ macro:
new Macro('try', function(code, { use_dynamic, error }) {
return (new Promise((resolve, reject) => {
let catch_clause, finally_clause;
if (LSymbol.is(code.cdr.car.car, 'catch')) {
catch_clause = code.cdr.car;
if (code.cdr.cdr instanceof Pair &&
LSymbol.is(code.cdr.cdr.car.car, 'finally')) {
finally_clause = code.cdr.cdr.car;
}
} else if (LSymbol.is(code.cdr.car.car, 'finally')) {
finally_clause = code.cdr.car;
}
if (!(finally_clause || catch_clause)) {
throw new Error('try: invalid syntax');
}
function finalize(result) {
resolve(result);
throw new IgnoreException('[CATCH]');
}
let next = (result, next) => {
next(result);
}
if (finally_clause) {
next = function(result, cont) {
// prevent infinite loop when finally throw exception
next = reject;
args.error = (e) => {
throw e;
};
unpromise(evaluate(new Pair(
new LSymbol('begin'),
finally_clause.cdr
), args), function() {
cont(result);
});
};
}
const args = {
env: this,
use_dynamic,
dynamic_env: this,
error: (e) => {
var env = this.inherit('try');
if (catch_clause) {
const name = catch_clause.cdr.car.car;
if (!(name instanceof LSymbol)) {
throw new Error('try: invalid syntax: catch require variable name');
}
env.set(name, e);
let catch_error;
var args = {
env,
use_dynamic,
dynamic_env: this,
error: (e) => {
reject(e);
throw new IgnoreException('[CATCH]');
}
};
const value = evaluate(new Pair(
new LSymbol('begin'),
catch_clause.cdr.cdr
), args);
unpromise(value, function(result) {
if (catch_error) {
reject(catch_error.error);
} else {
next(result, finalize);
}
});
} else {
next(value, finalize);
}
}
};
const value = evaluate(code.car, args);
unpromise(value, function(result) {
next(result, resolve);
}, args.error);
})).catch(e => {
console.log('CATCHED');
}).then(x => {
console.log('RESOLVED');
console.log({x});
return x;
});
})
And here is unpromise function (it’s basically maybe async like old jQuery differed).
function unpromise(value, fn = x => x, error = null) {
if (is_promise(value)) {
var ret = value.then(fn);
if (error === null) {
return ret;
} else {
return ret.catch(error);
}
}
if (value instanceof Array) {
return unpromise_array(value, fn, error);
}
if (is_plain_object(value)) {
return unpromise_object(value, fn, error);
}
return fn(value);
}
unpromise_object and unpromise_array is the equivalent for arrays and objects.
The important part is finalize function that is executed last:
function finalize(result) {
resolve(result);
throw new IgnoreException('[CATCH]');
}
evaluate can return promise or not depending on the scheme code used, some functions and macros are async some don’t. A more user friendly API exec always returns a Promise.
and evaluates catch exception and call error option:
function evaluate(code, { env, dynamic_env, use_dynamic, error = noop, ...rest } = {}) {
try {
// ... evaluation code
return result;
} catch (e) {
error && error.call(env, e, code);
}
}
and when executing this code:
(let ((result #f))
(try
(begin
(set! result 1)
(throw 'ZONK)
(set! result 2))
(catch (e)
(set! result 3)))
(t.is result 3))
(print (try (begin (throw 'ZONK) (print "x")) (catch (e) 10)))
the logs looks like this:
RESOLVED
{ x: true }
<<<EXCEPTION>>>
RESOLVED
{ x: undefined }
<<<EXCEPTION>>>
RESOLVED
{ x: LBigInteger { __value__: 10n, __type__: 'bigint' } }
10
Can you explain why I can’t reproduce this code with simple example like the first snippet, here is a copy:
function x() {
return new Promise(resolve => {
resolve();
throw new FakeError("<ZONK>");
});
}
await x() evaluates to undefined.
This function only resolves but doesn’t throw. Can someone explain how this is possible? I want to create a reproduction to report a bug to some library but I’m not able to create an example that works the same as my original code.
I was checking if the value returned by evaluate is a promise:
console.log(is_promise(value));
and for my base scheme code for try and catch it returns false.