-
-
Save xkizer/d63ac72ef48720c2066fbc9d3580ea90 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Thanks @jokeyrhyme, that's enlightening.