Add Support for Fibers (via JS Generators) #2207
Replies: 9 comments
|
This sounds fun, I'll give it a shot. |
|
If you need any assistance, let me know. I have done some thinking about this, so have a little brain dump about possible pain points
HTTP.get("user_details").then do |response|
HTTP.get("user_contacts?id=#{response.json[:user_id]}")
end.then do |response|
HTTP.get("user_details?id=#{response.json[:contact_id]}")
end.then do
puts "All finished"
endMay be translatable into something like this: response = HTTP.get("user_details").await
contacts = HTTP.get("user_contacts?id=#{response.json[:user_id]}").await
details = HTTP.get("user_details?id=#{response.json[:contact_id]}").await
puts "All finished"Which seems much cleaner. |
|
Ok first pass of experimentation and reading. Issues:
Marking this for discussion for further ideas, implementing this in the compiler shouldn't be too hard, it's just a matter of special casing Main issue is how useful this would actually be since you wouldn't be able to call |
|
@AnthonySuper damn, you beat me by few seconds 🐼 The main issue I see is the fact you can't call yield at any other call depth than the root of the generator function, any ideas to work around that? |
|
Well, after screwing around with it for a while, I don't have any easy answers, unfortunately. A common use case of fibers is something like: fiber = Fiber.new do
some_array.each do |elem|
Fiber.yield elem
end
endYou can sort of fake this by doing something like this: var Fiber = {
yieldValues: [],
yield: function(val) {
if(val) {
this.yieldValues.push(val);
}
},
[Symbol.iterator]: function*() {
yield* this.yieldValues[Symbol.iterator]();
}
}
some_array = [1, 2, 3];
function *gen(){
some_array.forEach(function(elem) {
Fiber.yield(elem);
});
yield* Fiber;
}
var f = gen();
var q = f.next();
while(! q.finished) {
console.log(q);
q = f.next();
}However, this ruins the entire point of Fibers, which is user-controlled scheduling, so it's pretty much useless. Implementing this might not actually be possible, which seems pretty unfortunate. |
|
Yeah, it seems to be a dead end for now, I'll leave the issue open in case someone comes up with a good idea. |
|
Almost implemented it locally but then realized that it's not that easy 😄 I've started with the following code, for some reason I thought that it may cover all cases. I've added two AST rewriters that convert:
So, Both nodes that compile these new nodes to JS are quite simple like this: module Opal
module Nodes
class FiberYieldNode < Base
handle :fiber_yield
chlidren :value
def compile
push "yield ", expr(value), ";"
end
end
end
endI've made it working with simple cases like f = Fiber.new do
i = 1
loop { Fiber.yield(i); i += 1 }
endGenerated code doesn't work.
From what I see this code can't work: a = function *() {
(function() { yield 123 })()
}
b = a()
b.next()Any ideas how to implement |
|
I think that Fiber.yield may not be something we can implement easily inside a block, unfortunately, because Javascript generators are heavily limited and sort of awkward. Like JS in general. |
|
The truth is that JS doesn't support coroutines, and simulate Fiber using either A possible approach might be compiling two versions of methods (generator and normal function) and decide which to run either at runtime or compile time. |
Uh oh!
There was an error while loading. Please reload this page.
JavaScript generators have increasingly good support among browsers. They're not 100% supported yet, but they will hopefully be soon.
I believe that it should be possible to implement much of Ruby's
Fibermodule using Generators. The semantics aren't exactly the same, but both allow for the same kind of suspended execution. Not only would this allow us to make the implementation ofEnumerableslightly cleaner, but this could pave the way to an eventual implementation of an async/await-like library using Fibers, which would make request-based code much easier.I don't really know exactly where we'd start with doing this, but I'd be willing to help in any way I can.
All reactions