Promises.jl
                                
                                 Promises.jl copied to clipboard
                                
                                    Promises.jl copied to clipboard
                            
                            
                            
                        Use JavaScript Promises syntax in Julia! (alpha)
Promises.jl: JavaScript-inspired async
Summary:
A
Promise{T}is a container for a value that will arrive in the future.You can await Promises, and you can chain processing steps with
.thenand.catch, each producing a newPromise.
Let's look at an example, using Promises.jl to download data in the background:
download_result = @async_promise begin
	# This will download the data, 
	#  write the result to a file, 
	#  and return the filename.
	Downloads.download("https://api.github.com/users/$(username)")
end
#=>  Promise{Any}( <pending> )
username = "JuliaLang"
The result is a pending promise: it might still running in the background!
download_result
#=>  Promise{Any}( <pending> )
You can use @await to wait for it to finish, and get its value:
@await download_result
#=>  "/var/folders/v_/fhpj9jn151d4p9c2fdw2gv780000gn/T/jl_LqoUCC"
Chaining with then
One cool feature of promises is chaining! Every promise has a then function, which can be used to add a new transformation to the chain, returning a new Promise.
download_result.then(
	filename -> read(filename, String)
).then(
	str -> JSON.parse(str)
)
#=>  
Promise{Dict{String, Any}}( <resolved>: Dict{String, Any} with 32 entries:
  "followers"         => 0
  "created_at"        => "2011-04-21T06:33:51Z"
  "repos_url"         => "https://api.github.com/users/JuliaLang/repos"
  "login"             => "JuliaLang"
  "gists_url"         => "https://api.github.com/users/JuliaLang/gists{/gist_id}"
  "public_repos"      => 36
  "following"         => 0
  "site_admin"        => false
  "name"              => "The Julia Programming Language"
  "location"          => nothing
  "blog"              => "https://julialang.org"
  "subscriptions_url" => "https://api.github.com/users/JuliaLang/subscriptions"
  "id"                => 743164
  ⋮                   => ⋮ )
Since the original Promise download_result was asynchronous, this newly created Promise is also asynchronous! By chaining the operations read and JSON.parse, you are "queing" them to run in the background.
Error handling: rejected Promises
A Promise can finish in two ways: it can ✓ resolve or it can ✗ reject. In both cases, the Promise{T} will store a value, either the resolved value (of type T) or the rejected value (often an error message).
When an error happens inside a Promise handler, it will reject:
bad_result = download_result.then(d -> sqrt(-1))
#=>  
Promise{Any}( <rejected>: 
DomainError with -1.0:
sqrt will only return a complex result if called with a complex argument. Try sqrt(Complex(x)).
Stacktrace:
 [1] throw_complex_domainerror(f::Symbol, x::Float64)
   @ Base.Math ./math.jl:33
 [2] sqrt
   @ ./math.jl:567 [inlined]
 [3] sqrt(x::Int64)
   @ Base.Math ./math.jl:1221
 [4] (::Main.var"#5#6"{typeof(sqrt)})(d::String)
   @ Main ~/Documents/Promises.jl/src/notebook.jl#==#34364f4d-e257-4c22-84ee-d8786a2c377c:1
 [5] promise_then(p::Promise{Any}, f::Main.var"#5#6"{typeof(sqrt)})
   @ Main.workspace#3 ~/Documents/Promises.jl/src/notebook.jl#==#49a8beb7-6a97-4c46-872e-e89822108f39:63
 [6] #18
   @ ~/Documents/Promises.jl/src/notebook.jl#==#49a8beb7-6a97-4c46-872e-e89822108f39:175 [inlined]
 )
If you @await a Promise that has rejected, the rejected value will be rethrown as an error:
@await bad_result
#=>  
DomainError with -1.0:
sqrt will only return a complex result if called with a complex argument. Try sqrt(Complex(x)).
Stacktrace:
 [1] throw_complex_domainerror(f::Symbol, x::Float64)
   @ Base.Math ./math.jl:33
 [2] sqrt
   @ ./math.jl:567 [inlined]
 [3] sqrt(x::Int64)
   @ Base.Math ./math.jl:1221
 [4] (::var"#5#6"{typeof(sqrt)})(d::String)
   @ Main ~/Documents/Promises.jl/src/notebook.jl#==#34364f4d-e257-4c22-84ee-d8786a2c377c:1
 [5] promise_then(p::Main.workspace#3.Promise{Any}, f::var"#5#6"{typeof(sqrt)})
   @ Main.workspace#3 ~/Documents/Promises.jl/src/notebook.jl#==#49a8beb7-6a97-4c46-872e-e89822108f39:63
 [6] #18
   @ ~/Documents/Promises.jl/src/notebook.jl#==#49a8beb7-6a97-4c46-872e-e89822108f39:175 [inlined]
Stacktrace:
 [1] fetch(p::Main.workspace#3.Promise{Any})
   @ Main.workspace#3 ~/Documents/Promises.jl/src/notebook.jl#==#49a8beb7-6a97-4c46-872e-e89822108f39:112
The Promise constructor
Remember that a promise can finish in two ways: it can ✓ resolve or it can ✗ reject. When creating a Promise by hand, this corresponds to the two functions passed in by the constructor, resolve and reject:
Promise{T=Any}(resolve, reject) -> begin
	if condition
		# Resolve the promise:
		resolve("Success!")
	else
		# Reject the promise
		reject("Something went wrong...")
	end
end)
yay_result = Promise((resolve, reject) -> resolve("🌟 yay!"))
#=>  Promise{Any}( <resolved>: "🌟 yay!" )
oopsie_result = Promise((res, rej) -> rej("oops!"))
#=>  Promise{Any}( <rejected>: "oops!" )
(A shorthand function is available to create promises that immediately reject or resolve, like we did above: Promises.resolve(value) and Promises.reject(value).)
Chaining errors with .catch
There are two special things about rejected values in chains:
- The .thenfunction of a rejected Promise will immediately reject, passing the value along.
Promise((res, rej) -> rej("oops!")).then(x -> x + 10).then(x -> x / 100)
#=>  Promise{Any}( <rejected>: "oops!" )
- The .catchis the opposite of.then: it is used to handle rejected values.
Promise((res, rej) -> rej("oops!")).then(x -> x + 10).catch(x -> 123)
#=>  Promise{Any}( <resolved>: 123 )
Here is a little table:
| .then | .catch | |
|---|---|---|
| On a resolved Promise: | Runs | Skipped | 
| On a rejected Promise: | Skipped | Runs | 
Promise{T} is a parametric type
Like in TypeScript, the Promise{T} can specify its resolve type. For example, Promise{String} is guaranteed to resolve to a String.
Promise{String}((res,rej) -> res("asdf"))
#=>  Promise{String}( <resolved>: "asdf" )
This information is available to the Julia compiler, which means that it can do smart stuff!
Core.Compiler.return_type(fetch, (Promise{String},))
#=>  String
Trying to resolve to another type will reject the Promise:
Promise{String}((res,rej) -> res(12341234))
#=>  
Promise{String}( <rejected>: 
ArgumentError: Can only resolve with values of type String.
Stacktrace:
 [1] (::Main.workspace#3.var"#resolve#20"{String, Promise{String}})(val::Int64)
   @ Main.workspace#3 ~/Documents/Promises.jl/src/notebook.jl#==#49a8beb7-6a97-4c46-872e-e89822108f39:21
 [2] (::Main.var"#25#26")(res::Main.workspace#3.var"#resolve#20"{String, Promise{String}}, rej::Function)
   @ Main ~/Documents/Promises.jl/src/notebook.jl#==#9d9179de-19b1-4f40-b816-454a8c071c3d:1
 [3] Promise{String}(f::Main.var"#25#26")
   @ Main.workspace#3 ~/Documents/Promises.jl/src/notebook.jl#==#49a8beb7-6a97-4c46-872e-e89822108f39:38
 )
Automatic types
Julia is smart, and it can automatically determine the type of chained Promises using static analysis!
typeof(
	Promise{String}((res,rej) -> res("asdf")).then(first)
)
#=>  Promise{Char}