Reputation: 4333
In my view I want:
:coffeescript
Gmap('#canvas').getAddressBounds request.term
which is defined in maps.js.coffee as
Gmap = (mapId) ->
getAddressBounds: (address) ->
data = []
$(mapId).gmap3
action: 'getAddress'
address: address
callback: (results) ->
return unless results
data = $.map results, (item) ->
bounds: item.geometry.bounds
data
This doesn't work though. First, there's a scope issue. The Gmap function is not visible to the script in the view. If I add the code directly to the view, Gmap is visible, but data always returns as [].
Upvotes: 3
Views: 3434
Reputation: 77426
What's going on is that you're treating asynchronous code as if it's synchronous. Some debug output might help you visualize this:
Gmap = (mapId) ->
getAddressBounds: (address) ->
data = []
console.log '1: Calling gmap3'
$(mapId).gmap3
action: 'getAddress'
address: address
callback: (results) ->
console.log '3: Callback called'
return unless results
data = $.map results, (item) ->
bounds: item.geometry.bounds
console.log '2: Returning data'
data
When you pass a callback, it could get called at any time. If it were called during the gmap3
function, then data
would indeed be set before being returned. But the reason gmap3
uses a callback to return its result, rather than just returning, is that the function is asynchronous—in particular, it calls the callback when the server responds to your query. The way that JavaScript does events, that means your callback is guaranteed not to be called until after all code has finished executing.
There's no way to wrap an asynchronous function in a synchronous one in JavaScript (or CoffeeScript); even running an infinite loop until your callback is called wouldn't work, because, again, the JS runtime doesn't handle events like server responses (or even user input events) until all code has finished executing. So all you can do is change your function, too, to use a callback:
Gmap = (mapId) ->
getAddressBounds: (address, cb) ->
$(mapId).gmap3
action: 'getAddress'
address: address
callback: (results) ->
return unless results
cb $.map results, (item) ->
bounds: item.geometry.bounds
Then call it like so:
Gmap('#canvas').getAddressBounds request.term, (data) -> console.log data
I talk a little bit more about the JS event model in my CoffeeScript book. Also, John Resig's How JavaScript Timers Work is a must-read. Asynchronicity takes some getting used to, but the benefits over multithreading are spectacular.
Upvotes: 5
Reputation: 32202
change Gmap = ... to window.Gmap = ....
will solve the problem. The reason is coffeescript wraps everything in an anonymous function. If you want a more feature full module system have a look here
https://github.com/jashkenas/coffee-script/wiki/Easy-modules-with-coffeescript
Upvotes: 2