Skip to content

Instantly share code, notes, and snippets.

@willmtemple
Created February 1, 2019 01:37
Show Gist options
  • Save willmtemple/985706a7d7e1aff0498e83e2ec5d750e to your computer and use it in GitHub Desktop.
Save willmtemple/985706a7d7e1aff0498e83e2ec5d750e to your computer and use it in GitHub Desktop.
// Notes on how to deal with callbacks in pxexec-runtime
/**
* In pxexec-runtime, program threads run in actual separate threads called
* Fibers. Fibers are concurrent in the same way that Operating System threads
* are concurrent: each has its own stack, and they are intermittently switched
* to give the appearance of multitasking. In some cases, they may run
* simultaneously on different processors.
*
* At the same time, Node events are processed on the global event-loop. All
* events (such as all asynchronous callbacks, event triggers, etc.) are
* triggered in the event loop. In normal Node programs, the main program body
* also runs on the event loop. But there's a problem. If the main body of our
* program uses a while (1) {} or other type of divergent behavior, it will
* block the event-loop permanently.
*
* The reason we use Fibers is to keep the main body of the program synchronous,
* but to avoid blocking the Node event-loop. By running our synchronous main
* program in a Fiber, the node event loop is free to process. However, this
* introduces some peculiarities with the processing of callbacks. Sometimes,
* in our Fiber, it is necessary to block the program body until a callback
* has been issued, or until a promise has been resolved. Additionally, when
* user code is invoked _as_ a callback, we must detach that code into a Fiber
* of its own.
*
* In core-exec, I have provided convenience functions for both scenarios that
* abstract away the logic of Fibers. The following three scenario guides will
* show you how to interface the synchronous code from pxexec user-code and
* the asynchronous code from Node libraries.
*/
// SCENARIO I - Promises
//
// When to use: a library returns a promise.
//
// Consider, a function with this prototype:
function foo(): Promise<number>
// If we need to call this function in pxexec, we have to have a function that
// will block the thread until the promise is resolved.
//
// This function is: require('/core-exec')._await<T>(Promise<T>) : T
//
// in English: import _await from core-exec and use it to convert Promise<T> to
// T
// Wrong
export function myFunc() {
var x: number = foo(); // <-- Type error!
return ...;
}
// Right
import { _await } from './core-exec';
export function myFunc() {
var x: number = _await(foo());
return ...;
}
// SCENARIO II - Waiting on an asynchronous callback
//
// When to use: a library function takes a callback
//
// Consider, a function with this prototype:
function getAPIValue(key: number, cb: (err: string, value: number) => void)
// If we need to wait for the callback to be invoked, we have to block the
// calling thread until the callback has completed. We do this by wrapping
// the invocation in a Promise (note, only if we _need_ the callback value
// parameter in order to return to the user's code!!)
// Wrong
export function myAPIHandler(key: number) : number {
getAPIValue(key, (err, value) => {
if (err) {
// handle error
} else {
return value; // <-- This will return _from the callback_, and then
} // myAPIHandler() will actually return undefined!!
});
}
// Right
import { _await } from './core-exec';
export function myAPIHandler(key: number): number {
return _await(new Promise((resolve, reject) => {
getAPIValue(key, (err, value) => {
if (err) {
// handle error
reject("error message");
} else {
resolve(value); // <-- resolve the promise with the return value
}
});
}));
}
// SCENARIO III - User-code callbacks
//
// When to use: the user needs to set a callback to be executed later
//
// Consider an EventEmitter within a library: we want to allow our users to
// configure an event handler for that event.
//
// A lot of our code has to do with letting users configure event handlers, such
// as the onButton methods in the Grove library. However, since students are
// writing synchronous callbacks, we need to detach these callbacks from the
// event loop when they are eventually executed. We do this using another
// function that I have provided in core-exec: `_detach(() => void)`
// Wrong
function onMessage(cb: (message: string) => void) {
someEventEmitter.on('event', cb); // <-- We've just set ourselves up for the
// event loop to call into user code!!
}
// Right
import { _detach } from './core-exec';
function onMessage(cb: (message: string) => void) {
someEventEmitter.on('event', (message: string) => {
// This function runs in the event loop.
// Then we use _detach to fork a Fiber (technically, a Future)
_detach(() => { // <-- _detach can only handle () => void, so wrap
// more complicated calls in a simple function.
// This function runs in a Fiber
cb(message);
})
});
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment