>
> But isn't that covered by the error handling of tasks? The same way as 
> when you use the "built-in" tasks like Http? Http gets around the general 
> issue of "what happens if this never returns" using the Http.Error value 
> Timeout. Maybe something similar could be used for Task Ports.


It's hard to disagree with that rationale. A "Timeout" and 
"UnexpectedValue" error would be all that is needed to handle cases where 
functions return no promise, a non-resolving promise, or a promise with an 
incorrect return type. 
 

On Tuesday, August 23, 2016 at 6:21:38 PM UTC-4, Tim Stewart wrote:
>
> But isn't that covered by the error handling of tasks? The same way as 
> when you use the "built-in" tasks like Http? Http gets around the general 
> issue of "what happens if this never returns" using the Http.Error value 
> Timeout. Maybe something similar could be used for Task Ports.
>
> In the same case with standard Cmd / Sub, you would just never receive the 
> Sub. It gives you LESS opportunity to handle the JS error through a timeout 
> or similar mechanism.
>
> Not to mention the added complexity when using Cmd / Sub of tracking 
> separate, parallel calls to the same port. You have the overhead not only 
> of managing two different entry points to your update function (as with any 
> Cmd/Sub pair for an essentially single asynchronous operation), but of 
> encapsulating some kind of state identifier that lets you match the 
> incoming Subs with whatever Cmd they related to in the first place. The use 
> of Tasks on the Elm side and Promises on the JS side makes this unnecessary.
>
> I know, Elm design decisions are based on concrete use cases not on waffly 
> hand-waving. I'll try and find time to write one up.
>
>
> On Tuesday, August 23, 2016 at 11:56:06 PM UTC+10, Erik Lott wrote:
>>
>> Although I'd love to be able to use and compose port commands like tasks, 
>> I'm not a big fan of this solution. 
>>
>> regular ports only let you send data off or receive data back, not both.
>>
>>
>> This is a good design decision. Ports isolate Elm from the underlaying 
>> language (javascript) and avoids creating dependencies on the outside 
>> implementation: you fire commands out of ports, and don't need to be 
>> concerned about what happens to that data once it's exited the port. You 
>> receive subscription messages from js, and don't have to be concerned about 
>> how they were generated.
>>
>> As soon as you send a command out of a port and expect something in 
>> return, you've created a dependency, and a new source of runtime errors 
>> that the compiler can't catch.
>>
>>
>> On Saturday, August 13, 2016 at 11:31:07 AM UTC-4, 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 elm-discuss+unsubscr...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Reply via email to