Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it possible to terminate a promise's code block from another promise?

I wrote this test program:

await Promise.anyof(
  Promise.allof((^5).map: {start { sleep 10; say "done $_" } }),
  Promise.in(5).then: { say 'ouch' }
);
sleep 10;

When the second promise times out it prints 'ouch' and the await exits, but the first promise's code block is still running. After five more seconds its five processes end and print 'done':

$ ./test1.p6
ouch
done 0
done 1
done 2
done 3
done 4

I tried to terminate the first promise assigning it to a variable and then calling the .break method from the second promise, but it doesn't work.

Is there a way to kill the first promise and the other five sub-promises it started?

like image 573
Fernando Santagata Avatar asked Oct 23 '18 18:10

Fernando Santagata


People also ask

How do I cancel a promise?

You can't cancel a Promise, but you can chain your promises with then, and reject at any point. new Promise((resolve, reject) => { // step 1 }) .then((result) => { if (!result) { // Reject the promise chain throw 'cancel'; } else { return ... // step 2 } }) .catch(err => { // cancelled });

How to break out of the promise chain in error handling?

Also keep in mind that if you want to break out of the chain in your error handler, it needs to return a rejected promise or throw an Error (which will be caught and wrapped in a rejected promise automatically). If you don't return a promise, then wraps the return value in a resolve promise for you.

How to reject a promise in a controller?

With AbortController It is possible to use abort controller to reject promise or resolve on your demand: let controller = new AbortController(); let task = new Promise((resolve, reject) => { // some logic ...

What happens if you don't return a promise in JavaScript?

If you don't return a promise, then wraps the return value in a resolve promise for you. This means that if you don't return anything, you are effectively returning a resolved promise for the value undefined.


2 Answers

You have to somehow tell the process that it doesn't have to finish.

my $cancel = Cancellation.new;

await Promise.anyof(
  Promise.allof(
    (^5).map: {
      last if $cancel.cancelled;

      start {
        sleep 10;
        say "done $_" unless $cancel.cancelled
      }
    }
  ),
  Promise.in(5).then: {
    $cancel.cancel;
    say 'ouch'
  }
);

If you want something like Promise.in() that can be cancelled, let's start by looking at the existing code.

method in(Promise:U: $seconds, :$scheduler = $*SCHEDULER) {
    my $p   := self.new(:$scheduler);
    my $vow := $p.vow;
    $scheduler.cue({ $vow.keep(True) }, :in($seconds));
    $p
}

Note that the result of $scheduler.cue is a Cancellation.

I am just going to wrap a Promise, and a Cancellation in a class for simplicity.
(I don't want to reimplement every method).

class Cancellable-Timer {
    has Promise      $.Promise;
    has              $!vow;
    has Cancellation $!cancel;

    method !SET-SELF ( $!promise, $!vow, $!cancel ){
        self
    }

    method in (::?CLASS:U: $seconds, :$scheduler = $*SCHEDULER) {
        my $p   := Promise.new(:$scheduler);
        my $vow := $p.vow;
        my $cancel = $scheduler.cue({ $vow.keep(True) }, :in($seconds));
        self.bless!SET-SELF($p,$vow,$cancel);
    }

    method cancel ( --> Nil ) {
        # potential concurrency problem
        if $!Promise.status == Planned {
            $!cancel.cancel;          # cancel the timer
            $!vow.break("cancelled"); # break the Promise
        }
    }

    method cancelled () {
        # Ignore any concurrency problems by using the Promise
        # as the sole source of truth.
        $!Promise.status ~~ PromiseStatus::Broken
    }
}

my $timer = Cancellable-Timer.in(1);
my $say = $timer.Promise.then: *.say;
Promise.in(0.1).then: {$timer.cancel};
await $say;

Note that the above class is just a rough first draft.

like image 169
Brad Gilbert Avatar answered Jan 01 '23 21:01

Brad Gilbert


Try with whenever:

$ perl6 -e '

react {
   for ^5 -> $num {
      whenever start { sleep 10 } {
         say "done $num"
      }
   }
   whenever Promise.in: 5 {
      say "ouch";
      done
   }
}

' ouch
like image 25
SmokeMachine Avatar answered Jan 01 '23 19:01

SmokeMachine