Race-condition while sending http request - race-condition

I have a situation that I need to dispatch two post requests, synchronously, and the second depends on the response of the first, the problem is that the second gets sent even before the first's response is allocated, thus sending wrong information:
update : msg -> model -> (model, Cmd msg)
update msg m =
case msg of
...
Submit -> (m,
send FirstResp <| post "/resource1"
(jsonBody <| encoderX m) int)
FirstResp (Ok x) -> ({m | pid = x},
send SecondResp <| post "/resource2"
(jsonBody <| encoderY m) int)
...
I tested it several times. If the server gives 3 in the first post, the pid gets sent as 0, but If I submit it again, the pid is sent as 3 and the answer from the server, 4, for example, is ignored.
How can I make the post to wait for the value to be allocated?

As data structures in elm are immutable {m | pid = x} doesn't change m but returns a new record. So you have no updated model when you pass it to your 2nd request.
Using {m | pid = x} twice would get you the result you are looking for (but it's not very beautiful)
FirstResp (Ok x) -> ({m | pid = x},
send SecondResp <| post "/resource2"
(jsonBody <| encoderY {m | pid = x}) int)
You can use let in to store the new model in a variable before you send the request. If you modify the model now you only have to look in one place.
FirstResp (Ok x) ->
let
newM = {m | pid = x}
in
(newM, send SecondResp <| post "/resource2"
(jsonBody <| encoderY newM) int)
If you don't need the result of the first request in your model the even better solution would be to chain the requests with Task.andThen. With this you don't need 2 separate messages (FirstResp, SecondResp).
request1 m =
post "/resource1" (jsonBody <| encoderX m) int)
|> Http.toTask
request2 m =
post "/resource1" (jsonBody <| encoderX m) int)
|> Http.toTask
Submit ->
( m
, request1 m
|> Task.andThen request2
|> Task.attempt Resp
)
Resp (Ok res2) ->
-- res2 is the result of your request2
If you need both results you can map them into a Tuple and extract it in the update function.
Submit ->
( m
, request1 m
|> Task.andThen
(\res1 -> request2 res1
|> Task.map ((,) res1)
)
|> Task.attempt Resp
)
Resp (Ok (res1, res2) ->
-- use res1 and res2
Elm packages - Task

Related

How do you iterate a List (Maybe a)

I have the following graphQL result:
[Just { details = Just "Engine failure at 33 seconds and loss of
vehicle", launch_year = Just "2006", links = Just { article_link =
Just
"https://www.space.com/2196-spacex-inaugural-falcon-1-rocket-lost-launch.html"
}, mission_name = Just "FalconSat" }]
Based on the following types:
type alias Launch =
{ mission_name : Maybe String
, details : Maybe String
, launch_year : Maybe String
, links : Maybe LaunchLinks
}
type alias Launches =
Maybe (List (Maybe Launch))
type alias LaunchLinks =
{ article_link : Maybe String
}
I want to List.map through and display the results in unordered list. I started with this:
renderLaunch : Launches -> Html Msg
renderLaunch launches =
div [] <|
case launches of
Nothing ->
[ text "Nothing here" ]
Just launch ->
launch
|> List.map (\x -> x)
|> ul []
But I keep getting this error:
This function cannot handle the argument sent through the (|>) pipe:
141| launch 142| |> List.map (\x
-> x) 143| |> ul []
^^^^^ The argument is:
List (Maybe Launch)
But (|>) is piping it a function that expects:
List (Html msg)
The problem is that the Just launch case needs to result in a List (Html msg) but the code results in a different type being returned.
When you are using List.map (\x -> x), it is essentially a no-op. You are iterating over a List (Maybe Launch) and returning the same thing. I'd recommend creating another function that takes a Maybe Launch value and use that as your mapping function. For example:
displayLaunch : Maybe Launch -> Html Msg
displayLaunch launch =
case launch of
Nothing -> text "No launch"
Just l -> text (Debug.toString l)
Now you can plug that into your mapping function:
Just launch ->
launch
|> List.map displayLaunch
|> ul []
But, whoops! Now you get a new error indicating:
The 2nd branch is:
Html Msg
But all the previous branches result in:
List (Html msg)
The problem here is that we are now returning a ul from the Just launch branch and we need to return a list of html. You can use List.singleton to create a list with just one item:
Just launch ->
launch
|> List.map displayLaunch
|> ul []
|> List.singleton

Retrieving a DOM value from Elm ports

My elm app uses an auto scrolling function, which gets the Y position of an element and uses Dom.Scroll.toY to scroll there.
Two do this, I set up two ports; a subscription and sender.
ports.elm
port setYofElementById : Maybe String -> Cmd msg
port getYofElementById : (Value -> msg) -> Sub msg
index.html
app.ports.setYofElementById.subscribe(function(id) {
var element = document.getElementById(id);
var rect = element.getBoundingClientRect();
app.ports.getYofElementById.send({"number": rect.top});
})
The listener is a subscription
subscriptions : Model -> Sub Msg
subscriptions model =
Ports.getYofElementById getYofElementById
getYofElementById : Decode.Value -> Msg
getYofElementById value =
let
result =
Decode.decodeValue bSimpleIntValueDecoder value
in
case result of
Ok simpleIntValue ->
SetSelectedElementYPosition (Just simpleIntValue.number)
Err id ->
SetSelectedElementYPosition Nothing
SetSelectedElementYPosition just sets the model.
Now, the action that executes this does two things: call Port.setYofElementById, then scrolls to the Y value in the model, assuming that it has already been set.
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
case msg of
ScrollToY idString ->
model
=> Cmd.batch
[ Ports.setYofElementById (Just idString)
, Task.attempt (always NoOp) <| Dom.Scroll.toY "ul" model.selectedElementYPosition
]
However, this doesn't happen sequentially. When the action first fires, nothing happens. If I fire it again, it scrolls to the location called for in the first action. So it seems like it is calling Dom.Scroll.toY before the value is set.
Is there a way to force the Cmds in ScrollToY to happen in sequence? Or is there a better way to do this in general?
You can get the Cmds to execute in sequence by making the second, the one that does the Dom.Scroll.toY, happen as a response to the first, the one that does the setYofElementById. The following update function accomplishes this:
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
case msg of
ScrollToY idString ->
(model, Ports.setYofElementById idString)
SetSelectedElementYPosition (Just newY) ->
(model, Task.attempt (always NoOp) <| Dom.Scroll.toY "ul" newY)
SetSelectedElementYPosition Nothing ->
(model, Cmd.none)
NoOp ->
(model, Cmd.none)
With the Cmds correctly sequenced, you will need to make sure that the newY argument to Dom.Scroll.toY is in the correct frame of reference to get the effect that you want.
I finally got this to work by tacking the action for Task.attempt (always NoOp) <| Dom.Scroll.toY "ul" model.selectedElementYPosition onto the action called by the subscription, not the action. That's the key.
With ports, the subscribe and send actions follow completely different pathways, so anything that reacts to a send from js to elm is not going to be referenced in the actions that go from elm to js.
In this case, since SetSelectedElementYPosition is being called from the subscription, you have to set the update there:
SetSelectedElementYPosition idString ->
({model | selectedElementYPosition = number }, Cmd.none)
|> andThen update GoToSelectedElementYPosition

Elm divide subscription?

I'm playing with Elm and WebRTC, so I made a listen port which gets some messages from js:
type alias Message =
{ channel : String
, data : String
}
port listen : (Message -> msg) -> Sub msg
Now I would like to be able to divide the messages to different parts of my app. For instance, the chat uses the "chat" channel, while the game logic uses "game".
Is it possible to create a listenTo String subscription that filters out the messages with the correct channel (only returning the data)? Or perhaps a different way of doing it?
Update:
What I currently have, is something like this:
In my main.elm I have an update that looks like this. It can receive messages (from rtc) itself, and send messages for chat to it. (I would later add a "ForGame" then too)
type Msg = Received WebRTC.Message | ForChat Chat.Msg
update : Msg -> Model -> (Model, Cmd Msg)
update msg model =
case msg of
Received message ->
let
_ = Debug.log ("Received message on \"" ++ message.channel ++ "\": " ++ message.data)
in
( model
, Cmd.none
)
ForChat msg ->
let
(chatModel, chatCmd) = Chat.update msg model.chat
in
({ model | chat = chatModel}, Cmd.map ForChat chatCmd)
Then I have subscriptions that combines all my subscriptions:
subscriptions : Model -> Sub Msg
subscriptions model =
Sub.batch
[ WebRTC.listen Received
, Sub.map ForChat <| Chat.subscriptions model.chat
]
In Chat.elm I have a similar structure, with an update that handles it's messages. The subscription of the chat listens to all messages from WebRTC, but filters only the ones with channel chat:
subscriptions : Model -> Sub Msg
subscriptions model = WebRTC.listen forChatMessages
forChatMessages : WebRTC.Message -> Msg
forChatMessages webrtcMessage =
if webrtcMessage.channel == "chat"
then
let
message = decodeMessage webrtcMessage.data
in
case message of
Ok msg -> Receive msg
Err error -> Debug.log ("Received unreadable message on chat channel \"" ++ toString webrtcMessage.data ++ "\" with error \"" ++ error ++ "\"") Ignore
else
Ignore
(Ignore is a Msg for chat, which just does nothing case msg of Ignore -> (model, Cmd.none). decodeMessage uses a decoder to decode a message decodeMessage : String -> Result String Message.)
I'm quite happy with this, because this way all logic for chat is in Chat.elm. So main.elm doesn't need to know what channels chat is using. Chat just follows the standard structure (Msg, update, view, subscriptions) and main forwards everything.
The only thing that's still not great, is that in Chat.elm I have the forChatMessages function. Used like: subscriptions model = WebRTC.listen forChatMessages. I would like to make this more reuseable, so it would become something like:
subscriptions model = WebRTC.listen for "chat" decodeMessage Receive Ignore
It would then be reusable by the game:
subscriptions model = WebRTC.listen for "game" decodeGameInfo UpdateInfo Ignore
Update 2:
I managed to generalize the forChatMessages function into:
for : String -> (String -> Result String d) -> (d -> msg) -> msg -> Message -> msg
for channel decoder good bad webrtcMessage =
if
webrtcMessage.channel == channel
then
let
decoded = decoder webrtcMessage.data
in
case decoded of
Ok data -> good data
Err error -> Debug.log ("Failed decoding message on " ++ channel ++ "channel \"" ++ toString webrtcMessage.data ++ "\" with error \"" ++ error ++ "\"") bad
else
bad
So I think I found the solution myself. Unless someones has comments on this. Perhaps there is a cleaner/nicer/better way of doing the same?
Let's say you have the following Msg definition:
type Msg
= Listen Message
| GameChannel String
| ChatChannel String
Your update function could then act upon the channel value and call update again with the correct channel, ignoring all channel values except for "game" and "chat":
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
case msg of
Listen message ->
case message.channel of
"game" ->
update (GameChannel message.data) model
"chat" ->
update (ChatChannel message.data) model
_ ->
model ! []
GameChannel data ->
...
ChatChannel data ->
...
Your subscription function would look something like this:
subscriptions : Model -> Sub Msg
subscriptions model =
listen Listen
I found a solution myself, and added it to the original question.
For clarity, this is the short version:
In my main.elm:
type Msg = Received WebRTC.Message | ForChat Chat.Msg
update : Msg -> Model -> (Model, Cmd Msg)
update msg model =
case msg of
Received message ->
let
_ = Debug.log ("Received message on \"" ++ message.channel ++ "\": " ++ message.data)
in
( model
, Cmd.none
)
ForChat msg ->
let
(chatModel, chatCmd) = Chat.update msg model.chat
in
({ model | chat = chatModel}, Cmd.map ForChat chatCmd)
subscriptions : Model -> Sub Msg
subscriptions model =
Sub.batch
[ WebRTC.listen Received
, Sub.map ForChat <| Chat.subscriptions model.chat
]
In Chat.elm:
subscriptions : Model -> Sub Msg
subscriptions model = WebRTC.listen <| for "game" decodeGameInfo UpdateInfo Ignore
In WebRTC.elm:
type alias Message =
{ channel : String
, data : String
}
port listen : (Message -> msg) -> Sub msg
for : String -> (String -> Result String d) -> (d -> msg) -> msg -> Message -> msg
for channel decoder good bad webrtcMessage =
if
webrtcMessage.channel == channel
then
let
decoded = decoder webrtcMessage.data
in
case decoded of
Ok data -> good data
Err error -> Debug.log ("Failed decoding message on " ++ channel ++ "channel \"" ++ toString webrtcMessage.data ++ "\" with error \"" ++ error ++ "\"") bad
else
bad

Elm StartApp decoding http request

I'm trying to decode a http request to pokéapi in Elm, using StartApp as a base. Though I'm getting an error I don't really know how to fix:
The right argument of (|>) is causing a type mismatch.
76│ Http.getString testUrl
77│ |> Task.map parseMon
78│> |> Task.map OnPokemonLoaded
(|>) is expecting the right argument to be a:
Task Http.Error (Result String Pokemon) -> a
But the right argument is:
Task Http.Error (Result Http.Error Pokemon) -> Task Http.Error Action
The code it's talking about is:
-- Fetching test mon
testUrl : String
testUrl = "http://pokeapi.co/api/v2/pokemon/1/"
fetchTest : Effects.Effects Action
fetchTest =
Http.getString testUrl
|> Task.map parseMon
|> Task.map OnPokemonLoaded --line 78
|> Effects.task
parseMon : String -> Result String Pokemon.Pokemon
parseMon json = Json.Decode.decodeString Pokemon.decoder json
OnPokemonLoaded is one of my actions: OnPokemonLoaded (Result Http.Error Pokemon). Pokemon.decoder is a simple json decoder: decoder : Decoder Pokemon.
I'm still new to Elm, and only just trying out StartApp and Effects. The error seems to explain the problem pretty well, but I'm still a little lost as to how it should work.
So, how should I request and decode the json properly?
The use of Http.getString and parseMon is unnecessary. Instead, you can use Http.get and pass your Json decoder, then map it to a Result to get the functionality you're after:
fetchTest : Effects.Effects Action
fetchTest =
Http.get Pokemon.decoder testUrl
|> Task.toResult
|> Task.map OnPokemonLoaded
|> Effects.task

Erlang Apple Push notification not getting response-error before disconnect

I'm currently testing a bit my push notification module.
When the Device Token is invalid, it disconnects...
According to Apple push notification developer documentation I should get an error-response packet just before the apple push server disconnect...
The thing is I get disconnected, but I do not get anything on the Socket just before that, and I need to know if the push failed because of a malformed push (so I can fix the bug), or an invalid device token (so I can remove it from the database).
Here's my code :
-module(pushiphone).
-behaviour(gen_server).
-export([start/1, init/1, handle_call/3, handle_cast/2, code_change/3, handle_info/2, terminate/2]).
-import(ssl, [connect/4]).
-record(push, {socket, state, cert, key}).
start(Provisioning) ->
gen_server:start_link(?MODULE, [Provisioning], []).
init([Provisioning]) ->
gen_server:cast(self(), {connect, Provisioning}),
{ok, #push{}}.
send(Socket, DT, Payload) ->
PayloadLen = length(Payload),
DTLen = size(DT),
PayloadBin = list_to_binary(Payload),
Packet = <<0:8,
DTLen:16/big,
DT/binary,
PayloadLen:16/big,
PayloadBin/binary>>,
ssl:send(Socket, Packet).
handle_call(_, _, P) ->
{noreply, P}.
handle_cast({connect, Provisioning}, P) ->
case Provisioning of
dev -> Address = "gateway.sandbox.push.apple.com";
prod -> Address = "gateway.push.apple.com"
end,
Port = 2195,
Cert="/apns-" ++ atom_to_list(Provisioning) ++ "-cert.pem",
Key="/apns-" ++ atom_to_list(Provisioning) ++ "-key.pem",
Options = [{certfile, Cert}, {keyfile, Key}, {password, "********"}, {mode, binary}, {active, true}],
Timeout = 1000,
{ok, Socket} = ssl:connect(Address, Port, Options, Timeout),
{noreply, P#push{socket=Socket}};
handle_cast(_, P) ->
{noreply, P}.
handle_info({ssl, Socket, Data}, P) ->
<<Command, Status, SomeID:32/big>> = Data,
io:fwrite("[PUSH][ERROR]: ~p / ~p / ~p~n", [Command, Status, SomeID]),
ssl:close(Socket),
{noreply, P};
handle_info({push, message, DT, Badge, [Message]}, P) ->
Payload = "{\"aps\":{\"alert\":\"" ++ Message ++ "\",\"badge\":" ++ Badge ++ ",\"sound\":\"" ++ "msg.caf" ++ "\"}}",
send(P#push.socket, DT, Payload),
{noreply, P};
handle_info({ssl_closed, _SslSocket}, P) ->
io:fwrite("SSL CLOSED !!!!!!~n"),
{stop, normal, P};
handle_info(AnythingElse, P) ->
io:fwrite("[ERROR][PUSH][ANYTHING ELSE] : ~p~n", [AnythingElse]),
{noreply, P}.
code_change(_, P, _) ->
{ok, P}.
terminate(_, _) ->
ok.
It works great when the payload and the deviceToken are both right. if deviceToken is invalid, it only get's disconnected.
Does anyone can spot the issue ? because after 4 hours of searching, I have only found out that I clearly can't !
Here's the error-response table :
Status code Description
0 No errors encountered
1 Processing error
2 Missing device token
3 Missing topic
4 Missing payload
5 Invalid token size
6 Invalid topic size
7 Invalid payload size
8 Invalid token
255 None (unknown)
You seem to be using the simple notification format as defined by figure 5-1 in the apple documentation you've linked to (judging by your send() function). When this format is used, no error response is provided when the request is malformed - you just get the disconnect.
To get the error response you should be using the enhanced notification format detailed in figure 5-2.