Last active
June 17, 2018 12:23
-
-
Save searls/77f055ad93e24c2be7711003f805bb0f to your computer and use it in GitHub Desktop.
Welp, I'm the proud owner of a hobbled-together framework built on top of ActionCable to simulate what had been an XHR request-response lifecycle. I'm just trying to use WebSockets to make a frequently-made request go faster, but boy did this feel like pulling teeth.
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
class StudyChannel < ApplicationCable::Channel | |
def subscribed | |
stream_for current_user | |
end | |
def handle_answer(data) | |
self.class.broadcast_to(current_user, { | |
:request_id => data["requestId"], | |
data["mode"] => { | |
data["id"] => PresentsReviewResult.new.call(current_user, data["id"], data["answer"]) | |
} | |
}) | |
end | |
end |
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
// app.ws - actioncable / websocket | |
app.ws = app.ws || {} | |
app.ws.cable = null | |
app.ws.init = () => { | |
app.ws.cable = app.ws.cable || ActionCable.createConsumer() | |
return app.ws.cable | |
} | |
app.ws.disconnect = () => { | |
if (app.ws.cable) { | |
app.ws.cable.disconnect() | |
} | |
} | |
app.ws.subscription = null | |
app.ws.subscriptionCallbacks = [] | |
app.ws.subscriptionConnected = false | |
// I really hate this subscriptions API. Basically, I want the subscription to be | |
// a singleton but I don't want to leak details about how the cable API works, | |
// so I'm collecting all the callbacks I receive and then invoking them either | |
// when the subscription is connected or immediately if the subscription is | |
// already connected. | |
app.ws.subscribe = (connectionCb) => { | |
app.ws.subscriptionCallbacks.push(connectionCb) | |
const cable = app.ws.init() | |
if (!app.ws.subscription) { | |
app.ws.subscription = cable.subscriptions.create('StudyChannel', { | |
connected () { | |
app.ws.subscriptionConnected = true | |
app.ws.subscriptionCallbacks.forEach(cb => | |
cb(app.ws.subscription) | |
) | |
app.ws.subscriptionCallbacks = [] | |
}, | |
received (data) { | |
app.ws.respond(humps.camelizeKeys(data)) | |
} | |
}) | |
} else if (app.ws.subscriptionConnected) { | |
app.ws.subscriptionCallbacks.forEach(cb => | |
cb(app.ws.subscription) | |
) | |
app.ws.subscriptionCallbacks = [] | |
} else { | |
cable.connect() | |
} | |
} | |
app.ws.unsubscribe = () => { | |
app.ws.disconnect() | |
app.ws.subscriptionConnected = false | |
} | |
app.ws.requestId = 0 | |
app.ws.requestCallbacks = {} | |
app.ws.request = (payload, cb) => { | |
const requestId = ++app.ws.requestId | |
app.ws.requestCallbacks[requestId] = cb | |
app.ws.subscribe(subscription => { | |
subscription.perform('handle_answer', _.extend({requestId}, payload)) | |
}) | |
} | |
app.ws.respond = (data) => { | |
const requestId = data.requestId | |
delete data.requestId | |
if (app.ws.requestCallbacks[requestId]) { | |
app.ws.requestCallbacks[requestId](data) | |
delete app.ws.requestCallbacks[requestId] | |
} | |
} |
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
// Actually using it looks something like this | |
app.ws.request({id: meaning.id, answer, mode}, (res) => { | |
const props = app.prop.merge(res) | |
app.store.save(props) | |
app.render(props) | |
app.route.redirect(`/app/${mode}/${meaning.id}/result`) | |
}) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
subscription.perform()
returns a "success" boolean that you can use instead of tracking the connected state manually. Simplified example: