Using callback-style from generators

Although it is easier to work with generator functions, ultimately, you will need to work with functions that do not use await-generator. In that case, callbacks are easier to use.

Using callback-style functions involves two steps. First, you create a callback passed to the function. Second, you tell await-generator to pause until the callback is called.

The first step can be done simply by writing yield. Upon yielding no values (or null), await-generator will immediately resume your function and send you a callback that will notify await-generator about function completion.

To achieve the second step, you have to yield a constant Await::ONCE. This tells await-generator to resume your function when the previous callback from yield is called.

function a(Closure $callback): void {
	// The other function that uses callbacks.
	// Let's assume this function will call $callback("foo") some time later.
}

function main(): Generator {
	$callback = yield;
	yield Await::ONCE;
}

Some callback-style async functions may also accept an $onError callback parameter. This callback can be created by calling Await::REJECT. Then Await::ONCE will call your function

function a(Closure $callback, Closure $onError): void {
	// The other function that uses callbacks.
	// Let's assume this function will call $callback("foo") some time later.
}

function main(): Generator {
	$callback = yield;
	yield Await::ONCE;
}

Example

Let's say we want to make a function that sleeps for 20 server ticks, or throws an exception if the task is cancelled:

use pocketmine\scheduler\Task;

public function sleep(): Generator {
	$resolve = yield;
	$reject = yield Await::REJECT;
	$task = new class($resolve, $reject) extends Task {
		private $resolve;
		private $reject;
		public function __construct($resolve, $reject) {
			$this->resolve = $resolve;
			$this->reject = $reject;
		}
		public function onRun(int $tick) {
			($this->resolve)();
		}
		public function onCancel() {
			($this->reject)(new \Exception("Task cancelled"));
		}
	};
	$this->getServer()->getScheduler()->scheduleDelayedTask($task, 20);

	yield Await::ONCE;
}

This is a bit complex indeed, but it gets handy once we have this function defined! Let's see what we can do with a countdown:

function countdown($player) {
	for($i = 10; $i > 0; $i--) {
		$player->sendMessage("$i seconds left");
		yield $this->sleep();
	}

	$player->sendMessage("Time's up!");
}

This is much simpler than using ClosureTask in a loop!