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