From acd8cc450b9d7071ad7ee18f61828776eebf3e68 Mon Sep 17 00:00:00 2001 From: Vadim Belman Date: Sun, 16 Jul 2023 05:42:11 -0400 Subject: [PATCH] Implement methods `andthen` and `orelse` on Promise The methods are intended to simplify conditional processing and chaining of kept or broken promises when used instead of the `then` method. `andthen` is fired only when its invocant promise is kept. If the invocant is broken then its cause becomes the cause of the promise returned by `andthen`: `orelse` is fired if its invocant is broken. If the invocant is kept then the promise returned by `orelse` is kept with invocant's result: If the invocant of `orelse` is broken the purpose of the method is to intercept the cause, handle it, and return a value to be used as the result of `orelse`-returned promise: --- src/core.c/Promise.pm6 | 66 +++++++++++++++++++++++++++++++++++------- 1 file changed, 55 insertions(+), 11 deletions(-) diff --git a/src/core.c/Promise.pm6 b/src/core.c/Promise.pm6 index e60e1502fd7..a82c1bf2988 100644 --- a/src/core.c/Promise.pm6 +++ b/src/core.c/Promise.pm6 @@ -204,6 +204,17 @@ my class Promise does Awaitable { } } + method !PLANNED-THEN(\then-promise, \vow, \then-code) { + # Push 2 entries to $!thens: something that starts the then code, + # and something that handles its exceptions. They will be sent to the + # scheduler when this promise is kept or broken. + nqp::bindattr(then-promise, Promise, '$!dynamic_context', nqp::ctx()); + nqp::push(nqp::ifnull($!thens, ($!thens := nqp::list)), then-code); + nqp::push($!thens, -> $ex { vow.break($ex) }); + nqp::unlock($!lock); + then-promise + } + method then(Promise:D: &code) { nqp::lock($!lock); if $!status == Broken || $!status == Kept { @@ -212,20 +223,53 @@ my class Promise does Awaitable { self.WHAT.start( { code(self) }, :$!scheduler); } else { - # Create a Promise, and push 2 entries to $!thens: something that - # starts the then code, and something that handles its exceptions. - # They will be sent to the scheduler when this promise is kept or - # broken. my $then-p := self.new(:$!scheduler); - nqp::bindattr($then-p, Promise, '$!dynamic_context', nqp::ctx()); my $vow := $then-p.vow; - nqp::push( - nqp::ifnull($!thens,($!thens := nqp::list)), - { my $*PROMISE := $then-p; $vow.keep(code(self)) } - ); - nqp::push($!thens, -> $ex { $vow.break($ex) }); + self!PLANNED-THEN($then-p, $vow, { my $*PROMISE := $then-p; $vow.keep(code(self)) } ) + } + } + + method andthen(Promise:D: &code) { + nqp::lock($!lock); + if $!status == Broken { + nqp::unlock($!lock); + self.WHAT.broken($!result) + } + elsif $!status == Kept { + # Already have the result, start immediately. + nqp::unlock($!lock); + self.WHAT.start( { code(self) }, :$!scheduler); + } + else { + my $then-p := self.new(:$!scheduler); + my $vow := $then-p.vow; + self!PLANNED-THEN( $then-p, + $vow, + { $!status == Kept + ?? do { my $*PROMISE := $then-p; $vow.keep(code(self)) } + !! $vow.break($!result) }) + } + } + + method orelse(Promise:D: &code) { + nqp::lock($!lock); + if $!status == Broken { + nqp::unlock($!lock); + self.WHAT.start( { code(self) }, :$!scheduler); + } + elsif $!status == Kept { + # Already have the result, start immediately. nqp::unlock($!lock); - $then-p + self.WHAT.kept($!result); + } + else { + my $then-p := self.new(:$!scheduler); + my $vow := $then-p.vow; + self!PLANNED-THEN( $then-p, + $vow, + { $!status == Kept + ?? $vow.keep($!result) + !! do { my $*PROMISE := $then-p; $vow.keep(code(self)) } }) } }