Promise
Preface:Scene Imagination lead to Promise
Imagine that you’re a top singer, and fans ask day and night for your upcoming song.
To get some relief, you promise to send it to them when it’s published. You give your fans a list. They can fill in their email addresses, so that when the song becomes available, all subscribed parties instantly receive it. And even if something goes very wrong, say, a fire in the studio, so that you can’t publish the song, they will still be notified.
For the first sight:1.What?Promise is made by the singer and
2.Why?it’s produced because the result can’t be immediate.
3.Efficacy?And promise connects singer and prospective subcribers. and makes it possible for the existence of delay.So it’s actually for dealing tasks that requires time.
And To map to concepts about Promise :
- “A producing code” is the singer. It can’t immediately give program-flow or another-code a result so that program-flow can proceed So it make a promise .
What may be the “producing code” :For instance, some code that loads the data over a network and that takes time - A “consuming code”(another-code that mentioned above) that wants the result of the “producing code” once it’s ready(for inputing paramters or something else) .These are the “fans”.
- A promise is a special JavaScript object that links the “producing code” and the “consuming code” together. In terms of our analogy: this is the “subscription list”. The “producing code” takes whatever time it needs to produce the promised result, and the “promise” makes that result available to all of the subscribed code when it’s ready.
Syntax
The constructor syntax for a promise object is:
1 | let promise = new Promise(function (resolve, reject) { |
To explain:
The function passed to
new Promise
is called the executor.When
new Promise
is created, the executor runs automatically(this is a func don’t need to be called by”()”). It contains the producing code which should eventually produce the result. In terms of the analogy above: the executor is the “singer”.Its arguments
resolve
andreject
are callbacks provided by JavaScript itself.When the executor obtains the result, be it soon or late, doesn’t matter,it should call one of these callbacks:
resolve(value)
— if the job is finished successfully, with resultvalue
.reject(error)
— if an error has occurred,error
is a Error object.
To supplementay:[!CAUTION]
The executor receives two arguments:
resolve
andreject
. These functions are pre-defined by the JavaScript engine, so we don’t need to create them. We should only call one of them when ready.
the executor may callsresolve("done")
to produce the result(remember above we said the executor should eventually produce the result? Yeah it can be done by calling resolve)
So to summarize: the executor runs automatically and attempts to perform a job. When it is finished with the attempt, it calls
resolve
if it was successful orreject
if there was an error.[!Important]
1.So Promise constructor requires a function that contains two built-in callbacks as paramter And the function is the executor
2.executor carries two callback that is ready to be called and then produces the result
3.result exists relating to two possible state
4.what is eventually returned by this syntax constructor is actually a object which is the Promise. And result is just been carried with.
To proceed:
The promise
object returned by the new Promise
constructor has these internal properties:
state
— initially"pending"
, then changes to either"fulfilled"
whenresolve
is called or"rejected"
whenreject
is called.result
— initiallyundefined
, then changes tovalue
whenresolve(value)
is called orerror
whenreject(error)
is called.
So the executor eventually moves promise
to one of these states:
and if we successfully produce the result which the consuming codes are craving for. It turned like this:
A promise that is either fulfilled or rejected is called “settled”, as opposed to an initially “pending” promise.
To be noted (fundamental)
- There can be only a single result or an error
The executor should call only one resolve
or one reject
. Any state change is final.
All further calls of resolve
and reject
are ignored:
1 | let promise = new Promise(function (resolve, reject) { |
Also, resolve
/reject
expect only one argument (or none) and will ignore additional arguments.
A good habit:Reject with
Error
objectsIn case something goes wrong, the executor should call
reject
. That can be done with any type of argument (just likeresolve
). But it is recommended to useError
objects (or objects that inherit fromError
)
To explain:reject(new Errror('wrong'))
is better thanreject('wrong')
[!NOTE]
The
state
andresult
are internalWhich means :
state
andresult
of the Promise object can’t be directly accessed.Accessing has to be done bymethods.then
/.catch
/.finally
(They are described below.)
Consumers:then catch
As you might have comprehensed by now: A Promise object serves as a link between the executor (the “producing code” or “singer”) and the consuming functions (the “fans”) which will receive the result or error.It actually bond these two togther by something.
Something(mentioned above) is methods .then
and .catch
.
then
The syntax is:
1 | promise.then( |
See? The promise comes from a producing code (executor)
(In case you forget):
1 | let promise = new Promise(function (resolve, reject) { |
And it serves as a link And it uses then/catch methods to connect with the consuming functions (that desperate to receive a result or a error)
You can see the consuming functions as the one above
1 | function(result) { /* handle a successful result */ }, |
[!NOTE]
the then methods could specify two functions:1.one for the success result 2. one for the rejected error
[!TIP]
If we’re interested to see successful completions(what’s in resolve)
we can provide only one function argument to.then
:
1
2
3
4
5 let promise = new Promise((resolve) => {
setTimeout(() => resolve("done!"), 1000);
});
promise.then(alert); // shows "done!" after 1 secondbecause in then handler promise.result is passed as paramter
catch
catch only for error
If we’re interested only in errors, then we can use null
as the first argument: .then(null, errorHandlingFunction)
. Or we can use .catch(errorHandlingFunction)
, which is exactly the same:
1 | let promise = new Promise((resolve, reject) => { |
[!CAUTION]
The call
.catch(f)
is a complete analog of.then(null, f)
, it’s just a shorthand.
finally
The call .finally(f)
is similar to .then(f, f)
in the sense that f
runs always, when the promise is settled: be it resolve or reject.
Why need finally
The idea of finally
is to set up a handler for performing cleanup/finalizing after the previous operations are complete.finally
的想法是设置一个处理程序,用于在前面的操作完成后执行清理/终结。
Because in real-life code after promise is produced,something should be close or clean up.
[!NOTE]
a handler is function in promise-chain but a object in Proxy(,handler) Its translation in Chinese is “处理程序”
E.g. stopping loading indicators, closing no longer needed connections, etc.
Think of it as a party finisher. No matter was a party good or bad, how many friends were in it, we still need (or at least should) do a cleanup after it.
syntax
1 | new Promise((resolve, reject) => { |
Differences between finally(f) and then(f,f)
A
finally
handler has no arguments.[!NOTE]
In
finally
we don’t know and we shouldn’t know whether the promise is successful or not.That’s all right, as our task is usually to perform “general” finalizing procedures.(=producing promise procedure cause when you created the state-change is final)
take a look at the example above: as you can see, the
finally
handler has no arguments, and the promise outcome is handled by the next handler.A
finally
handler “passes through” the result or error to the next suitable handler.For instance, here the result is passed through
finally
tothen
:1
2
3
4
5new Promise((resolve, reject) => {
setTimeout(() => resolve("value"), 2000);
})
.finally(() => alert("Promise ready")) // triggers first
.then((result) => alert(result)); // <-- .then shows "value"[!IMPORTANT]
That’s very convenient, because
finally
is not meant to process a promise result (the treasure produced). As said, it’s a place to do generic cleanup, no matter what the outcome was.A
finally
handler also shouldn’t return anything. If it does, the returned value is silently ignored.[!WARNING]
The only exception to this rule is when a
finally
handler throws an error. Then this error goes to the next handler, instead of any previous outcome.
[!IMPORTANT]
Sum up finally :
finally is just a dutiful cleaner lady.
Only pass promise.result and clean
don’t receive (paramter) don’t return don’t know (what’s going on about promise)
it runs regardlessly and ensures that a piece of code executes no matter what the outcome of the promise is.
But If cleaning-machine malfunctions throw a Error
[!TIP]
handlers
To supplementary: finally and then-catch (which means all handlers)all operate on Promise object
And The premise that they run is Promise object which just produced,or you can say the state of Promise object is settled
In below’s case they run immediately
1
2
3
4 // the promise becomes resolved immediately upon creation
let promise = new Promise((resolve) => resolve("done!"));
promise.then(alert); // done! (shows up right now)Note that this makes promises more powerful than the real life “subscription list” scenario. If the singer has already released their song before a person signs up on the subscription list, they probably won’t receive that song. Subscriptions in real life must be done prior to the event.
订阅发生在事件发生之前
[!IMPORTANT]
Sum up handler:
1.operate on Promise object
2.when “.handler” ‘s front is a promise fully formed,handler runs immediately
eg1:loadScript
1 | function loadScript(src, callback) { |
[!NOTE]
- the onload and onerror methods of A script element (Actually HTML-Element is built-in object) implies what will happen when script loads successfully or fail
- To put callback directly in this position(right after “()=>”) doesn’t invoke the callback immediately; it just assigns it as the handler for the event.So when the event occurs callback is invoked automatically
Let’s rewrite it using Promises.
The new function loadScript
will not require a callback. Instead, it will create and return a Promise object that resolves when the loading is complete. The outer code can add handlers (subscribing functions) to it using .then
:
1 | function loadScript(src) { |
1 | let promise = loadScript( |
Promises | Callbacks |
---|---|
Promises allow us to do things in the natural order. First, we run loadScript(script) , and .then we write what to do with the result. |
We must have a callback function at our disposal when calling loadScript(script, callback) . In other words, we must know what to do with the result before loadScript is called. |
We can call .then on a Promise as many times as we want. Each time, we’re adding a new “fan”, a new subscribing function, to the “subscription list”. |
There can be only one callback. |
eg2:delay using promise
1 | function delay(ms) { |
To supplementay
For a Promise
the state change of a Promise is a one-time thing.
And there are only two results:alive or dead
fulfilled or rejected