Skip to content

Instantly share code, notes, and snippets.

@xkizer
Last active April 28, 2019 15:50
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save xkizer/d63ac72ef48720c2066fbc9d3580ea90 to your computer and use it in GitHub Desktop.
Save xkizer/d63ac72ef48720c2066fbc9d3580ea90 to your computer and use it in GitHub Desktop.
let globalVar = 10;
function globalFunction(param1: Record<string, unknown>) {
const innerObj = {
key: 'value',
obj: {} as Record<string, unknown>
};
// Threaded functions will run in a new thread every time they're called. They behave like
// async functions, in that they return a Promise which is resolved when the thread exits
// gracefully, and rejected when the thread exits unexpectedly. Additionally, it supports
// the use of await keyword to wait for promises to resolve.
//
// This function takes a message port as the second argument. This is used to send messages
// between a thread and another. An async function can use as many ports as it needs, and these
// can be passed to the thread in many ways, including through another message port.
//
// The behaviour of message ports is not defined here
//
// The "threaded" keyword defines this function as threaded, much as the "async" keyword defines
// an async function as async.
threaded function threadSafeFn(obj1: Record<string, any>, msgPort: Port<any>) {
// Read-only access, all are fine
let newNum = globalVar + 90;
const newObj = {...innerObj, key2: 'value 2'};
// Direct modification
newObj.key = 'new value'; // Fine, we're modifying our local copy
newObj.obj.key = 'value'; // TypeError. Even though newObj is in the local context, newObj.obj was created outside the local context
obj1.modified = true; // Works fine, obj1 was passed directly to function
obj1.prop.modified = true; // TypeError. obj1 was passed directly to function, but not obj1.prop
param1.modified = true; // TypeError. param1 belongs to higher context
innerObj.key = 'another value'; // TypeError. param1 belongs to higher context
globalVar = 30; // TypeError. globalVar belongs to higher context
// Indirect modification
setGlobalVar(90); // TypeError. this will cause the modification of an external variable
setProp(param1, 'key', 'new value'); // TypeError. This will modify an external object
// Exiting unexpectedly
throw new Error('Something terrible happened'); // uncaught error will terminate the thread with error
Promise.reject(new Error('Another thing happened')); // Uncaught promise rejection will terminate the thread with error
const users = await getUsers(); // Just like an await in a normal async function
// Even though the users object is returned by a function defined outside the local context,
// it was created within that function or within a function that that function called.
// This means it was created in the context of this thread, making this thread the owner.
// So we can modify it.
users.push({id: 'random', name: 'User'});
// Listen for messages from any another thread. msg can be anything, including
// objects. Same modification rules apply as above. Message can be anything, inclusing another
// message port!
msgPort.onMessage(msg => {
// Do something with msg
});
// Send a message to all other threads listening on this message port. No other thread can modify
// the object sent by this thread. Message can be anything, including another message port!
msgPort.postMessage({
a: 1,
b: 2
});
// Performing async
setTimeout(() => {
// This will never be executed, as the function returns (and kills this thread)
// before this has a chance to execute
newNum = 120;
});
getUsers().then((newUsers) => {
// This will never happen, as the thread would have died before the promise has
// a chance to resolve. If you want to make sure this happens, await it.
users.concat(newUsers);
}).catch((e) => {
// This will also never happen.
console.error(e);
});
const users$ = getUsers();
// Returning a threaded function signifies the end of life for the thread. This will
// terminate the thread, and transfer the context to the calling thread. In this case
// we have returned a function. Anything can be returned (strings, undefined, object, etc).
// Unless this returned function is also defined as threaded, calling it will run it in the
// thread of the caller, not in this thread.
//
// Returning kills the thread. All pending asynchrnous actions will be killed. Any unresolved
// promise will be cancelled. If the promise, or a reference to it, is returned, it will be
// rejected on the next tick.
//
// Any thread explicitly started by this function or another function that it called (and so on)
// will be killed
return function () {
// Behavioir depends on who would be calling this returned function
// If this function is called on the same thread as the creator of innerObj,
// everything is fine. Else, it will be TypeError.
innerObj.key = 'yet another value';
// This will succeed in the thread that originally called the "threadSafeFn"
// because the context of "threadSafeFn" is transferred to the calling thread
// after it exits. This will fail in any other thread, except in a few exceptions.
// One such exceptions is if the calling thread (thread B) is also the child thread
// of another thread (thread A), and thread B returned, thereby transferring its
// context (which now includes the context of this thread) to the parent.
newObj.key2 = 'a new value';
// A reference to a promise whose execution started in this thread. This promise
// has been cancelled when the thread exited. Therefore, this promise will be
// rejected immediately
return users$;
}
}
}
function setGlobalVar(value: number) {
globalVar = value;
}
function setProp(obj: Record<string, unknown>, key: string, value: unknown) {
obj[key] = value;
}
declare class Port<T> {
onMessage(cb: (msg: T) => void): void;
postMessage(msg: T): void;
};
async function getUsers(): Promise<User[]> {
const usersStr = await fetch('/users');
const users: User[] = await usersStr.json();
return users;
}
type User = {
name: string,
id: string
};
@xkizer
Copy link
Author

xkizer commented Apr 28, 2019

Thanks @jokeyrhyme, that's enlightening.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment