-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
c1be2e7
commit f6bc650
Showing
5 changed files
with
165 additions
and
0 deletions.
There are no files selected for viewing
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
Userland algebraic effects in JS | ||
================================ | ||
|
||
This repository is highly experimental and is absolutely not production ready. | ||
|
||
It is a tentative implementation of the concepts behind algebraic effects in JS. | ||
|
||
### The Problem and how Algebraic Effects can help | ||
|
||
The JS computation model basically works the following way: | ||
- JS has a task queue, and runs tasks to completion. | ||
- Tasks can be scheduled either directly by the host environment (browser, node) in reaction to native events: `postmessage`, `fs.readFile` returning, etc, or indirectly when JS calls scheduling functions such as `setTimeout` or `new Promise`. [1] | ||
|
||
At any point in time, JS is either idle, or running a synchronous task. JS itself isn't capable of doing asynchronous work - it merely delegates asynchronous tasks to the host environment, which itself does async work in the background, and enqueues a new task to handle the results (usually using callbacks). | ||
|
||
The problem is that | ||
|
||
|
||
|
||
|
||
[1] Scheduling is actually more complex since there are actually multiple tasks queues and a priority system, depending on the host environment, but for all intents and purposes here, the task queue is opaque. |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
import asyncHooks from 'async_hooks'; | ||
import fs from 'fs'; | ||
import util from 'util'; | ||
|
||
const membranes = new Map(); | ||
const resources = new Map(); | ||
|
||
// We use process.nextTick as a scheduler, but maybe new Promise or other microtask scheduler can be used elsewhere | ||
const scheduleMicroTask = fn => process.nextTick(fn); | ||
|
||
const debugLog = (...args) => | ||
fs.writeSync( | ||
1, | ||
`[executionAsyncId=${asyncHooks.executionAsyncId()}] ${util.format( | ||
...args, | ||
)}\n`, | ||
); | ||
|
||
class Resource { | ||
constructor(resourceId, type, parentResourceId, membraneId) { | ||
this.resourceId = resourceId; | ||
this.type = type; | ||
this.parentResourceId = parentResourceId; | ||
this.membraneId = membraneId; | ||
} | ||
} | ||
|
||
class Membrane { | ||
constructor(membraneId, name) { | ||
this.membraneId = membraneId; | ||
this.name = name; | ||
} | ||
} | ||
|
||
const findMembraneId = parentResourceId => { | ||
const membrane = membranes.find(parentResourceId); | ||
if (membrane) { | ||
return membrane.membraneId; | ||
} | ||
const parentResource = resources.find(parentResourceId); | ||
if (!parentResource) { | ||
return null; | ||
} | ||
return parentResource.membraneId; | ||
}; | ||
|
||
const init = (resourceId, type, parentResourceId) => { | ||
const membraneId = findMembraneId(parentResourceId); | ||
if (membraneId !== null) { | ||
const resource = new Resource( | ||
resourceId, | ||
type, | ||
parentResourceId, | ||
membraneId, | ||
); | ||
resources.set(resourceId, resource); | ||
} | ||
}; | ||
|
||
const destroy = resourceId => { | ||
if (resources.has(resourceId)) { | ||
resources.delete(resourceId); | ||
} | ||
if (membranes.has(resourceId)) { | ||
membranes.delete(resourceId); | ||
} | ||
}; | ||
|
||
const fork = (name, fn) => { | ||
const parentResourceId = asyncHooks.executionAsyncId(); | ||
scheduleMicroTask(() => { | ||
const membraneId = asyncHooks.executionAsyncId(); | ||
membranes.set(membraneId, new Membrane(membraneId, name, parentResourceId)); | ||
fn(); | ||
}); | ||
}; | ||
|
||
const asyncHook = asyncHooks.createHook({ init, destroy }); | ||
|
||
asyncHook.enable(); | ||
|
||
export { fork, debugLog }; |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
import asyncHooks from 'async_hooks'; | ||
import util from 'util'; | ||
import fs from 'fs'; | ||
|
||
let asyncHook = null; | ||
|
||
let resourcesMap = new Map(); | ||
|
||
const log = (...args) => | ||
fs.writeSync( | ||
1, | ||
`[executionAsyncId=${asyncHooks.executionAsyncId()}] ${util.format( | ||
...args, | ||
)}\n`, | ||
); | ||
|
||
const getRootAsyncId = (asyncId = asyncHooks.executionAsyncId()) => { | ||
if (!resourcesMap.has(asyncId)) { | ||
return asyncId; | ||
} | ||
return getRootAsyncId(resourcesMap.get(asyncId).parent); | ||
}; | ||
|
||
const init = (asyncId, type, triggerAsyncId) => { | ||
resourcesMap.set(asyncId, { | ||
parent: triggerAsyncId, | ||
root: getRootAsyncId(triggerAsyncId), | ||
}); | ||
log('init', { asyncId, type, triggerAsyncId, rootAsyncId: getRootAsyncId() }); | ||
}; | ||
const destroy = asyncId => log('destroy', { asyncId }); | ||
|
||
const install = () => { | ||
if (asyncHook) { | ||
return; | ||
} | ||
asyncHook = asyncHooks.createHook({ init, destroy }); | ||
asyncHook.enable(); | ||
}; | ||
|
||
install(); | ||
|
||
log('outside everything (top level)'); | ||
setTimeout(() => { | ||
log('inside first setTimeout'); | ||
setTimeout(() => { | ||
log('inside second setTimeout'); | ||
}, 1); | ||
}, 1); | ||
process.nextTick(() => { | ||
log('inside first process.nextTick'); | ||
process.nextTick(() => log('inside second process.nextTick')); | ||
}); |
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
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
import { Membrane } from './Membrane'; | ||
|
||
(async () => { | ||
const membrane = new Membrane('async'); | ||
await Promise.resolve(); | ||
membrane.destroy(); | ||
})(); |