Gathering detailed insights and metrics for @andrewmacmurray/elm-concurrent-task
Gathering detailed insights and metrics for @andrewmacmurray/elm-concurrent-task
Gathering detailed insights and metrics for @andrewmacmurray/elm-concurrent-task
Gathering detailed insights and metrics for @andrewmacmurray/elm-concurrent-task
npm install @andrewmacmurray/elm-concurrent-task
Typescript
Module System
Node Version
NPM Version
Cumulative downloads
Total Downloads
Last Day
0%
NaN
Compared to previous day
Last Week
0%
NaN
Compared to previous week
Last Month
0%
NaN
Compared to previous month
Last Year
0%
NaN
Compared to previous year
5
Task
api - run a tree of tasks concurrently.Task Ports
- call JavaScript functions as tasks.This package is heavily inspired by elm-pages' BackendTask
and is intended to be a standalone implementation that can be dropped into any Elm app - big kudos to Dillon for the idea.
See the examples for more things you can do!
View the elm-package docs here.
Task.map2
, Task.map3
+ In elm/core
run each subtask in sequence.
Whilst it's possible to run these subtasks concurrently as separate Cmd
s, it can be a lot of wiring and boilerplate, including:
Elm Task Parallel handles this nicely but only at the top level (sub tasks cannot be parallelised).
And what if you want to speed up a complex nested task like:
1Task.map2 combine 2 (task1 3 |> Task.andThen task2 4 |> Task.andThen 5 (\res -> 6 Task.map2 combine 7 (task3 res) 8 task4 9 ) 10 ) 11 (Task.map2 combine 12 task5 13 task6 14 )
This is the elm equivalent of "callback hell".
This library helps you do this with a lot less boilerplate.
Sometimes you want to call JavaScript from elm in order. For example sequencing updates to localstorage:
NOTE: See a full working localstorage example here.
1import ConcurrentTask exposing (ConcurrentTask) 2import Json.Decode as Decode exposing (Decoder) 3import Json.Encode as Encode 4 5 6 7-- Preferences 8 9 10type alias Preferences = 11 { contrast : Int 12 , brightness : Int 13 } 14 15 16setContrast : Int -> ConcurrentTask Error () 17setContrast contrast = 18 getItem "preferences" decodePreferences 19 |> ConcurrentTask.map (\preferences -> { preferences | contrast = contrast }) 20 |> ConcurrentTask.andThen (encodePreferences >> setItem "preferences") 21 22 23encodePreferences : Preferences -> Encode.Value 24encodePreferences p = 25 Encode.object 26 [ ( "contrast", Encode.int p.contrast ) 27 , ( "brightness", Encode.int p.brightness ) 28 ] 29 30 31decodePreferences : Decoder Preferences 32decodePreferences = 33 Decode.map2 Preferences 34 (Decode.field "contrast" Decode.int) 35 (Decode.field "brightness" Decode.int) 36 37 38 39-- Localstorage 40 41 42type Error 43 = NoValue 44 | ReadBlocked 45 | DecodeError Decode.Error 46 | WriteError String 47 48 49getItem : String -> Decoder a -> ConcurrentTask Error a 50getItem key decoder = 51 ConcurrentTask.define 52 { function = "localstorage:getItem" 53 , expect = ConcurrentTask.expectString 54 , errors = ConcurrentTask.expectErrors decodeReadErrors 55 , args = Encode.object [ ( "key", Encode.string key ) ] 56 } 57 |> ConcurrentTask.map (Decode.decodeString decoder >> Result.mapError DecodeError) 58 |> ConcurrentTask.andThen ConcurrentTask.fromResult 59 60 61setItem : String -> Encode.Value -> ConcurrentTask Error () 62setItem key value = 63 ConcurrentTask.define 64 { function = "localstorage:setItem" 65 , expect = ConcurrentTask.expectWhatever 66 , errors = ConcurrentTask.expectThrows WriteError 67 , args = 68 Encode.object 69 [ ( "key", Encode.string key ) 70 , ( "value", Encode.string (Encode.encode 0 value) ) 71 ] 72 } 73 74 75decodeReadErrors : Decoder Error 76decodeReadErrors = 77 Decode.string 78 |> Decode.andThen 79 (\reason -> 80 case reason of 81 "NO_VALUE" -> 82 Decode.succeed NoValue 83 84 "READ_BLOCKED" -> 85 Decode.succeed ReadBlocked 86 87 _ -> 88 Decode.fail ("Unrecognized Read Error: " ++ reason) 89 )
Other implementations of Task Ports
rely on either:
ServiceWorkers
- intercept certain http requests and call custom JavaScript from the service worker.XMLHttpRequest
- Modify methods on the global XMLHttpRequest
to intercept http requests and call custom JavaScript.Both methods are not ideal (modifying global methods is pretty dodgy), and neither are portable to other environments like node (ServiceWorker
and XMLHttpRequest
are only native in the browser and require pollyfills).
elm-concurrent-task
uses plain ports and a bit of wiring to create a nice Task api.
This makes it dependency free - so more portable (🤓) and less likely to break (😄).
Because elm-concurrent-task
uses a different type to elm/core
Task
it's unfortunately not compatible with elm/core
Task
s.
However, there are a number of tasks built into the JavaScript runner and supporting modules that should cover a large amount of the existing functionality of elm/core
Task
s.
Check out the built-ins for more details:
Install the elm package with
elm install andrewMacmurray/elm-concurrent-task
Install the JavaScript/TypeScript runner with
npm install @andrewmacmurray/elm-concurrent-task
Your Elm program needs:
A single ConcurrentTask.Pool
in your Model
to keep track of each task attempt:
1type alias Model = 2 { tasks : ConcurrentTask.Pool Msg Error Success 3 }
2 Msg
s to handle task updates:
1type Msg 2 = OnProgress ( ConcurrentTask.Pool Msg Error Success, Cmd Msg ) -- updates task progress 3 | OnComplete (ConcurrentTask.Response Error Success) -- called when a task completes
2 ports with the following signatures:
1port send : Decode.Value -> Cmd msg 2port receive : (Decode.Value -> msg) -> Sub msg
Here's a simple complete program that fetches 3 resources concurrently:
1port module Example exposing (main) 2 3import ConcurrentTask exposing (ConcurrentTask) 4import ConcurrentTask.Http as Http 5import Json.Decode as Decode 6 7 8type alias Model = 9 { tasks : ConcurrentTask.Pool Msg Error Titles 10 } 11 12 13type Msg 14 = OnProgress ( ConcurrentTask.Pool Msg Error Titles, Cmd Msg ) 15 | OnComplete (ConcurrentTask.Response Error Titles) 16 17 18type alias Error = 19 Http.Error 20 21 22 23-- Get All Titles Task 24 25 26type alias Titles = 27 { todo : String 28 , post : String 29 , album : String 30 } 31 32 33getAllTitles : ConcurrentTask Error Titles 34getAllTitles = 35 ConcurrentTask.succeed Titles 36 |> ConcurrentTask.andMap (getTitle "/todos/1") 37 |> ConcurrentTask.andMap (getTitle "/posts/1") 38 |> ConcurrentTask.andMap (getTitle "/albums/1") 39 40 41getTitle : String -> ConcurrentTask Error String 42getTitle path = 43 Http.get 44 { url = "https://jsonplaceholder.typicode.com" ++ path 45 , headers = [] 46 , expect = Http.expectJson (Decode.field "title" Decode.string) 47 , timeout = Nothing 48 } 49 50 51 52-- Program 53 54 55init : ( Model, Cmd Msg ) 56init = 57 let 58 ( tasks, cmd ) = 59 ConcurrentTask.attempt 60 { send = send 61 , pool = ConcurrentTask.pool 62 , onComplete = OnComplete 63 } 64 getAllTitles 65 in 66 ( { tasks = tasks }, cmd ) 67 68 69update : Msg -> Model -> ( Model, Cmd Msg ) 70update msg model = 71 case msg of 72 OnComplete response -> 73 let 74 _ = 75 Debug.log "response" response 76 in 77 ( model, Cmd.none ) 78 79 OnProgress ( tasks, cmd ) -> 80 ( { model | tasks = tasks }, cmd ) 81 82 83subscriptions : Model -> Sub Msg 84subscriptions model = 85 ConcurrentTask.onProgress 86 { send = send 87 , receive = receive 88 , onProgress = OnProgress 89 } 90 model.tasks 91 92 93port send : Decode.Value -> Cmd msg 94 95 96port receive : (Decode.Value -> msg) -> Sub msg 97 98 99main : Program {} Model Msg 100main = 101 Platform.worker 102 { init = always init 103 , update = update 104 , subscriptions = subscriptions 105 }
Connect the runner to your Elm app (the runner supports both import
and require
syntax):
1import * as ConcurrentTask from "@andrewmacmurray/elm-concurrent-task"; 2 3const app = Elm.Main.init({}); 4 5ConcurrentTask.register({ 6 tasks: {}, 7 ports: { 8 send: app.ports.send, 9 receive: app.ports.receive, 10 }, 11});
The value passed to tasks
is an object of task names to functions (the functions can return plain synchronous values or promises)
e.g. tasks for reading and writing to localStorage:
1const tasks = {
2 "localstorage:getItem": (args) => localStorage.getItem(args.key),
3 "localstorage:setItem": (args) => localStorage.setItem(args.key, args.item),
4};
NOTE: for a more complete localStorage
integration with proper error handling check out the localstorage example.
Each send
and receive
port pair only support one ConcurrentTask.Pool
subscribed at a time.
Weird things can happen if you have two or more ConcurrentTask.Pool
s using the same ports at the same time.
Generally this should not be needed, but if you have a use-case, please leave an issue.
Install Dependencies:
npm install
Run the tests with:
npm test
To preview any changes, try some of the examples in the examples folder.
View the docs locally with:
npm run docs
elm bump
to bump the elm version.version
in package.json to match the new elm version.No vulnerabilities found.
No security vulnerabilities found.