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.
