A task-port to handle javascript promises would be optimal for sure, but 
for now I use a callback structure so the JS can call back into whatever 
port I want.  Definitely a hack but it also fulfills the situations where 
there can be multiple callbacks and not just one.


On Saturday, August 13, 2016 at 12:52:11 PM UTC-6, José Lorenzo Rodríguez 
wrote:
>
> I would really love this, the lack of Task ports is the main reason I 
> fallback to creating native modules.
>
> On Saturday, August 13, 2016 at 5:31:07 PM UTC+2, James Wilson wrote:
>>
>> The problem
>>
>> ports as they stand are fundamentally incompatible with Tasks. Being 
>> backed by Cmd's, they are harder to compose. A frustration of mine is that 
>> often we are directed to "just use ports" when a proper interface to some 
>> native API is not yet available, but this leads to our Msg types growing 
>> and more significant changes being required when eventually the proper 
>> interface is made available.
>>
>> Also, many JS interop things I find myself wanting to do are 
>> fundamentally one-shot functions which I expect a result back into Elm from 
>> immediately, or otherwise just want to compose with other Task based 
>> things. Some examples that come to mind of one-shot tasks you may want to 
>> compose rather than use the streaming interface that ports provide:
>>
>>    - Getting items from local/sessionStorage
>>    - .. really, most things involving working with the Web API that 
>>    arent yet implemented in Elm.
>>    - Embedding JS widgets into Elm elements
>>    - Using a JS library for doing things like hashing passwords or 
>>    obtaining some data back from some custom service
>>    - Interacting with things like Electron for creating apps that can 
>>    run in the desktop and interact with the filesystem etc.
>>    
>>
>> The solution
>>
>> Task ports. The idea is that these are defined the same way that Ports in 
>> elm currently are, but they return a Task type rather than a Cmd or Sub 
>> type. On the JS Side, we attach a function to the Elm app that returns a 
>> Promise, and on the Elm side we wait for the Promise returned to reject or 
>> resolve, and marhsall the error or result from the promise into the error 
>> or result type required by the Task type of the port.
>>
>> Let's see how this might work:
>>
>>
>> *Ports.elm:*
>>
>> port apiSession: Task String SessionId
>>
>>
>>
>> *Main.elm:*
>>
>> import Ports
>> import Json.Decode as Decode
>> import Task exposing (andThen)
>>
>>
>> -- get an API session from JS land and make an http request using it
>> -- given some path and a decoder to decipher the result:
>> apiRequest : String -> Decoder a -> Task ApiError a
>> apiRequest path decoder =
>>   let
>>     headers sessId =
>>         [ ("Content-Type", "application/json")
>>         , ("MyApp-SessionId", sessId)
>>         ]
>>
>>
>>     req sessId = Http.send Http.defaultSettings
>>         { verb = "POST"
>>         , headers = headers sessId
>>         , url = path
>>         }
>>
>>
>>     decodeResponse res = Decode.decodeString decoder -- ...handle error 
>> etc
>>   in
>>     Ports.apiSession `andThen` req `andThen` decodeResponse
>>
>>
>> *App.js:*
>>
>> Elm.Main.ports.apiSession = function(){
>>     return new Promise(function(resolve,reject){
>>
>>
>>         var sess = localStorage.getItem("sessionId");
>>         if(!sess) reject("NO_SESSION");
>>         else resolve(sess);
>>
>>
>>     });
>> }
>>
>> var app = Elm.Main.fullscreen();
>>
>>
>>
>>
>> Here, we use a tiny bit of JS to access localStorage and pull out a 
>> session ID. This function is used whenever the apiRequest Task is performed 
>> in Elm, and composes nicely into our apiRequest without the need for a 
>> complicated effect manager or threading a sessionId through everywhere just 
>> because we need to get it from a Cmd based port.
>>
>> One of the nice things about this is that there is minimal refactoring to 
>> do for those things that do eventually receive coverage in the Elm Web API 
>> - you're just swapping out Tasks for other Tasks. As the Web API will 
>> always be changing, I think that having a nice way to make JS polyfills 
>> like this will always have some value, let alone for interacting with 
>> libraries written in JS that haven't or won't ever be ported to Elm.
>>
>> Elm would continue to make the same guarantees as with other ports; if 
>> the task port can't marshall the response back into Elm an error would be 
>> thrown along the same lines as is currently done via ports.
>>
>> Summary
>>
>> - regular ports only let you send data off or receive data back, not both.
>> - Cmd's and Sub's are not composable
>> - Task based ports allow you to create a new Task that is backed by JS
>> - Task based ports allow for better composition and less friction when 
>> the backing JS is eventually implemented in Elm.
>>
>> I'd love to hear what people think about this. Perhaps I'm missing some 
>> big issues with the idea for instance, or maybe it's an awesome idea :) 
>> What do you all think?
>>
>>

-- 
You received this message because you are subscribed to the Google Groups "Elm 
Discuss" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
For more options, visit https://groups.google.com/d/optout.

Reply via email to