UNPKG

4.97 kBtext/coffeescriptView Raw
1Fiber = require 'fibers'
2Future = require 'fibers/future'
3
4# We replace Future's version of Function.prototype.future with our own.
5# Keep a reference so we can use theirs later.
6functionWithFiberReturningFuture = Function::future
7
8module.exports = fibrous = (fn) ->
9 futureFn = functionWithFiberReturningFuture.call(fn) # handles all the heavy lifting of inheriting an existing fiber when appropriate
10 # Don't use (args...) here because asyncFn.length == 0 when we do.
11 # A common (albeit short-sighted) pattern used in node.js code is checking
12 # Fn.length > 0 to determine if a function is async (accepts a callback).
13 asyncFn = (cb) ->
14 args = if 1 <= arguments.length then Array.prototype.slice.call(arguments, 0) else []
15 callback = args.pop()
16 throw new Error("Fibrous method expects a callback") unless callback instanceof Function
17 future = futureFn.apply(@, args)
18 future.resolve callback
19 Object.defineProperty asyncFn, '__fibrousFn__', value: fn, enumerable: false
20 Object.defineProperty asyncFn, '__fibrousFutureFn__', value: futureFn, enumerable: false
21 asyncFn
22
23
24futureize = (asyncFn) ->
25 (args...) ->
26 fnThis = @ is asyncFn and global or @
27
28 # Don't create unnecessary fibers and futures
29 return asyncFn.__fibrousFutureFn__.apply(fnThis, args) if asyncFn.__fibrousFutureFn__
30
31 future = new Future
32 args.push(future.resolver())
33 try
34 asyncFn.apply(fnThis, args)
35 catch e
36 # Ensure synchronous errors are returned via the future
37 future.throw(e)
38 future
39
40synchronize = (asyncFn) ->
41 (args...) ->
42 # When calling a fibrous function synchronously, we don't need to create a future
43 return asyncFn.__fibrousFn__.apply(@ is asyncFn and global or @, args) if asyncFn.__fibrousFn__
44
45 asyncFn.future.apply(@, args).wait()
46
47proxyAll = (src, target, proxyFn) ->
48 for key in Object.keys(src) # Gives back the keys on this object, not on prototypes
49 do (key) ->
50 return if Object::[key]? # Ignore any rewrites of toString, etc which can cause problems
51 return if Object.getOwnPropertyDescriptor(src, key).get? # getter methods can have unintentional side effects when called in the wrong context
52 return unless typeof src[key] is 'function' # getter methods may throw an exception in some contexts
53
54 target[key] = proxyFn(key)
55
56 target
57
58proxyBuilder = (futureOrSync) ->
59 (that) ->
60 result =
61 if typeof(that) is 'function'
62 func = (futureOrSync is 'future' and futureize or synchronize)(that)
63 func.__proto__ = Object.getPrototypeOf(that)[futureOrSync] if Object.getPrototypeOf(that) isnt Function.prototype
64 func
65 else
66 Object.create(Object.getPrototypeOf(that) and Object.getPrototypeOf(that)[futureOrSync] or Object::)
67
68 result.that = that
69
70 proxyAll that, result, (key) ->
71 (args...) ->
72 # Relookup the method every time to pick up reassignments of key on obj or an instance
73 @that[key][futureOrSync].apply(@that, args)
74
75
76defineMemoizedPerInstanceProperty = (target, propertyName, factory) ->
77 cacheKey = "__fibrous#{propertyName}__"
78 Object.defineProperty target, propertyName,
79 enumerable: false
80 set: (value) ->
81 delete @[cacheKey]
82 Object.defineProperty @, propertyName, value: value, writable:true, configurable: true, enumerable: true # allow overriding the property turning back to default behavior
83 get: ->
84 unless @hasOwnProperty(cacheKey) and @[cacheKey]
85 Object.defineProperty @, cacheKey, value: factory(@), writable: true, configurable: true, enumerable: false # ensure the cached version is not enumerable
86 @[cacheKey]
87
88# Mixin sync and future to Object and Function
89for base in [Object::, Function::]
90 defineMemoizedPerInstanceProperty(base, 'future', proxyBuilder('future'))
91 defineMemoizedPerInstanceProperty(base, 'sync', proxyBuilder('sync'))
92
93# Wait for all provided futures to resolve:
94#
95# result = fibrous.wait(future)
96# results = fibrous.wait(future1, future2)
97# results = fibrous.wait([future1, future2])
98fibrous.wait = (futures...) ->
99 getResults = (futureOrArray) ->
100 return futureOrArray.get() if (futureOrArray instanceof Future)
101 getResults(i) for i in futureOrArray
102
103 Future.wait(futures...)
104 result = getResults(futures) # return an array of the results
105 result = result[0] if result.length == 1
106 result
107
108
109# Connect middleware ensures that all following request handlers run in a Fiber.
110#
111# To use with Express:
112#
113# var fibrous = require('fibrous');
114# var app = express.createServer();
115# app.use(fibrous.middleware);
116#
117# Note that non-Fiber cooperative async operations will run outside the fiber.
118fibrous.middleware = (req, res, next) ->
119 process.nextTick ->
120 Fiber ->
121 try
122 next()
123 catch e
124 # We expect any errors which bubble up the fiber will be handled by the router
125 console.error('Unexpected error bubble up to the top of the fiber:', e?.stack or e)
126 .run()