Maybe it could accept either a promise or a (callback) function and behave 
accordingly? Anyone targeting IE could then decide whether to use callbacks 
or add a shim. 

On Thursday, August 25, 2016 at 7:46:55 AM UTC+10, James Wilson wrote:
>
> Good point, I had node in the back of my mind but somehow forgot what they 
> did!
> On 24 Aug 2016 10:45 p.m., "Maxwell Gurewitz" <[email protected] 
> <javascript:>> wrote:
>
>> It'd be more in line with community standards if the callback followed 
>> the node convention
>>
>> App.ports.myTaskFunc = function(val, cb) {
>>   cb(null, val + 2);
>> }
>>
>>
>>
>>
>>
>> On Wednesday, August 24, 2016 at 11:57:04 AM UTC-7, James Wilson wrote:
>>>
>>> It wouldn't be hard to provide a promise shim, although I'm not sure how 
>>> I feel about that. 
>>>
>>> Callbacks would be the well supported option, although the interface to 
>>> promises maps better. Promises can only resolve/reject once, always return 
>>> something, and the function attached to the task port could just return a 
>>> plain old value too, which would be equivalent to resolving a promise 
>>> immediately with that value. This means that for synchronous calls the user 
>>> can just provide a regular function to the JS side of the task port and not 
>>> do anything Promisy at all. If we want to provide callbacks instead I would 
>>> be tempted to provide 2 - one for resolve and 1 for rejection, and then add 
>>> the promise logic (first one to be called wins) behind the scenes.
>>>
>>> In the promise scenario, these are all valid:
>>>
>>> App.ports.myTaskFunc = function(val){
>>>     return (val + 2) //equivalent to returning a Promise that is 
>>> resolved to (val+2) immediately
>>> }
>>>
>>> App.ports.myTaskFunc = function(val){
>>>     return new Promise(function(resolve){
>>>         resolve(val+2)
>>>         reject("err") // this is ignored since we've already resolved.
>>>     });
>>> }
>>>
>>> App.ports.myTaskFunc = function(val){
>>>     return Promise.resolve(val + 2)
>>> }
>>>
>>>
>>> And the equivalent callback style might look like:
>>>
>>> App.ports.myTaskFunc = function(val,resolve,reject){
>>>     resolve(val+2);
>>>     reject("err") // this is ignored since we've already resolved.
>>> }
>>>
>>>
>>>
>>>
>>>
>>> On Wednesday, 24 August 2016 18:42:16 UTC+1, Maxwell Gurewitz wrote:
>>>>
>>>> My only comment would be that the interface should not rely on 
>>>> promises, which are not supported by IE.  Instead it should use node style 
>>>> callbacks.
>>>>
>>>> On Saturday, August 13, 2016 at 8:31:07 AM UTC-7, 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 a topic in the 
>> Google Groups "Elm Discuss" group.
>> To unsubscribe from this topic, visit 
>> https://groups.google.com/d/topic/elm-discuss/TjWoacZobWw/unsubscribe.
>> To unsubscribe from this group and all its topics, send an email to 
>> [email protected] <javascript:>.
>> For more options, visit https://groups.google.com/d/optout.
>>
>

-- 
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