Support tracing on rejection?
plesner opened this issue · 4 comments
I had to implement a service for a company recently and used promises from this library as the core way to organize asynchronous operations. It works great and is running happily in production.
However, as soon as you then-chain more than a few promises, understanding the reasons for rejections becomes super painful and time-consuming. One thing that would have helped me a lot during development was if I could trace how rejection propagates through chains of promises. I would suggest adding support for this in some form.
Here's a really simple example of what I have in mind. Say you create a chain of promises,
p0 = Promise()
p1 = p0.then(partial(add, 1))
p2 = p1.then(partial(add, 2))
p3 = p2.then(partial(add, 3))
and then you reject the "root",
p0.reject(MyError('D'))
If I only have p3
somewhere else in the program it becomes really hard to understand that it was rejected through p0
. What I'd like is a way to get a trace from p3
, something like
File "/home/tundra/Workspace/python-promise/tests/test_tracing.py", line 54, in run_test_chained_rejection
p0.reject(MyError('D'))
--- caused by ---
... repeated frames elided ...
File "/home/tundra/Workspace/python-promise/tests/test_tracing.py", line 54, in run_test_chained_rejection
return run_test_chained_rejection(remaining_calls - 1)
File "/home/tundra/Workspace/python-promise/tests/test_tracing.py", line 56, in run_test_chained_rejection
p1 = p0.then(partial(add, 1))
--- caused by ---
... repeated frames elided ...
File "/home/tundra/Workspace/python-promise/tests/test_tracing.py", line 54, in run_test_chained_rejection
return run_test_chained_rejection(remaining_calls - 1)
File "/home/tundra/Workspace/python-promise/tests/test_tracing.py", line 57, in run_test_chained_rejection
p2 = p1.then(partial(add, 2))
--- caused by ---
... repeated frames elided ...
File "/home/tundra/Workspace/python-promise/tests/test_tracing.py", line 54, in run_test_chained_rejection
return run_test_chained_rejection(remaining_calls - 1)
File "/home/tundra/Workspace/python-promise/tests/test_tracing.py", line 58, in run_test_chained_rejection
p3 = p2.then(partial(add, 3))
This way you can follow the chain of promises backwards and get to the original cause. If I was to build support for this that you could optionally enable (because it may be too expensive to keep enabled always) do you think you might be interested in including it in the library?
Yeah, that would be a great addition to the library! :)
Cool. I'm already playing around with an implementation (plesner#1), I'll send you a pull request when it's complete. I've made it as a separate module, promise.tracing
, that works exactly the same as promise
(almost all the code is shared) so you can easily flip between the two. But I'm happy to do it in a different way if you have any suggestions.
I've spent some time trying to implement this and it's turned out to be trickier than I expected.
Collecting stack traces and building the trace isn't too hard but the top of all traces will be 2-3 frames from within the promise library which makes them cluttered and hard to read. I haven't been able to find a way to avoid those without adding extra arguments to init, reject, etc. which feels like a hack and pollutes the external api.
I may be able to come up with a different approach but if I don't follow up with an implementation it'll be because I can't find a solution to this problem.