Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ES6 Generators: poor stack trace from iterator.throw(err)

The ES6 method: iterator.throw(err) is often described as injecting an exception as though it occurred at the yield statement in the generator. The problem is that the stack trace for this exception does not contain any reference to the file/line for the yield statement or even the function that it is in. Rather, the stack trace seems to only be generated when the exception object is constructed, which is not inside the generator.

The question is: how can I get the location of the offending yield statement, in a stack trace or otherwise?

function* one_of_many_generators() {
    // ...
    yield ajax(url);    // <-- what I need in the stack trace
    // ...
}

function outer() {
    var iterator = one_of_many_generators();
    iterator.next();    // runs to the first yield

    // inject exception at the yield statement
    iterator.throw(Error("error"));   // <-- top of stack trace shows here
}

Though this issue is not specific to Promises, they may make it easier to picture the problem. In my case, I am using a task system with generators and promises. The hypothetical function ajax() returns a Promise, and if that is rejected then the error is converted into a throw at the yield statement using this mechanism.

The stack traces in the debugger are pretty useless because I cannot find a way to get the function, file, or line number for the yield statement where this injection is occurring. Calling iterator.throw(err) is treated like a rethrow, and does not get new stack information, so it only shows a location inside the ajax() function which can be called from many places, and by throwing a new error in outer() like in the example above, the same throw line shows for all errors. Neither gives a hint as to what generator function was being executed for debugging the error.


I am using Chrome v42.

like image 263
Avalanche Avatar asked May 17 '15 18:05

Avalanche


1 Answers

Iterators and promises don't mix very well (yet) - you're essentially yielding a promise that then fails outside the loop.

You can get round this by passing the results of the promise back to the generator, something like:

function* one_of_many_generators() {
    // ...
    var promiseResult = yield ajax(url);    // <-- what I need in the stack trace

    // Now we're back in the generator with the result of the promise
    if(notHappyWithResult(promiseResult))
        throw new Error('Reason result is bad');
    // ...
}

async function outer() {
    var iterator = one_of_many_generators();
    let prms = iterator.next();    // runs to the first yield

    // Wait for the promise to finish
    let result = await prms;

    // Pass the result back to the generator
    let whatever = iterator.next(result);
}

Only: this is kind of what async and await do anyway (these keywords being just syntactic sugar for a generator of promises with results passed back) and if you use them a regular try-catch will work.

iterator.throw is mainly a way of stopping the iteration, not injecting an exception back into it - top of the stack is still wherever you create the Error.

Finally, coming soon in Chrome are async iterators - these are pretty powerful and are all about iterations of promises.

like image 192
Keith Avatar answered Nov 02 '22 22:11

Keith