Network Security Internet Technology Development Database Servers Mobile Phone Android Software Apple Software Computer Software News IT Information

In addition to Weibo, there is also WeChat

Please pay attention

WeChat public account

Shulou

Introduction to the principle and basic usage of Promise

2025-01-15 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Development >

Share

Shulou(Shulou.com)06/03 Report--

This article mainly introduces "introduction to the principle and basic usage of Promise". In daily operation, I believe that many people have doubts about the introduction of the principle and basic usage of Promise. The editor consulted all kinds of materials and sorted out simple and easy-to-use operation methods. I hope it will be helpful for you to answer the doubts about "introduction to the principle and basic usage of Promise". Next, please follow the editor to study!

1. Promise basic usage 1.1 basic usage new Promise (function (resolve, reject) {/ / call the resolve or reject method} after the pending asynchronous logic / / processing finishes)

Creating a new promise is easy, as long as you need to new a promise object. So promise is essentially a function that takes a function as an argument and returns a promise object, which provides the basis for chained calls.

In fact, the mission of the Promise function is to build its instances and manage them for us. These instances have the following three states:

Pending: initial state, bit fulfillment or rejection

Fulfilled: means that the operation completed successfully

Rejected: it means that the operation failed

The Promise object in pending state may return a value in fulfilled state, or it may be rejected (reject) for some reason (exception information). When either occurs, the handling method (handlers) bound by the then method of the Promise object is called, and the then method specifies the callback functions of the resolve method and the reject method, respectively.

Var promise = new Promise (function (resolve, reject) {if (/ * asynchronous operation succeeded * /) {resolve (value);} else {reject (error);}}); promise.then (function (value) {/ / if resolve method is called, execute this function}, function (value) {/ / if reject method is called, execute this function})

The above code clearly shows how the promise object runs. Let's look at another example:

Var getJSON = function (url) {var promise = new Promise (function (resolve, reject) {var client = new XMLHttpRequest (); client.open ("GET", url); client. > client.responseType = "json"; client.setRequestHeader ("Accept", "application/json"); client.send (); function handler () {if (this.status = = 200) {resolve (this.response) } else {reject (new Error (this.statusText));}}; return promise;}; getJSON ("/ posts.json"). Then (function (json) {console.log ('Contents:' + json);}, function (error) {console.error ('error', error);})

In the above code, both the resolve and reject methods are called with parameters. Their arguments are passed to the callback function. The parameter of the reject method is usually an instance of the Error object, while the parameter of the resolve method may be another Promise instance in addition to the normal value, such as the following.

Var p1 = new Promise (function (resolve, reject) {/ /... Some code}); var p2 = new Promise (function (resolve, reject) {/ /. Some code resolve (P1);})

In the above code, p1 and p2 are both instances of Promise, but the resolve method of p2 takes p1 as a parameter, and the state of p1 is passed to p2. If the state of p1 is pending when called, the callback function of p2 will wait for the state of p1 to change; if the state of p1 is already fulfilled or rejected, then the callback function of p2 will be executed immediately

1.2 promise capture error

The Promise.prototype.catch method is an alias for Promise.prototype.then (null, rejection) and is used to specify the callback function when an error occurs

GetJSON ("/ visa.json") .then (function (result) {/ / some code}) .catch (function (error) {/ / handle the error that occurred when the previous callback function was running console.log ('error!' , error);})

Errors in the Promise object are bubbling in nature and are passed backwards until they are caught. That is, the error is always caught by the next catch statement

GetJSON ("/ visa.json") .then (function (json) {return json.name;}) .then (function (name) {/ / proceed}) .catch (function (error) {/ / handle errors thrown by any previous then function})

Promise.all method

The Promise.all method is used to wrap multiple Promise instances into a new Promise instance.

Var p = Promise.all ([p1pm p2memp3])

In the above code, the Promise.all method takes an array as a parameter, and p1, p2, and p3 are all instances of the Promise object. The argument to the Promise.all method is not necessarily an array, but it must have an iterator interface, and each member returned is an instance of Promise. )

The state of p is determined by p1, p2 and p3, which is divided into two cases.

Only when the states of p1, p2 and p3 become fulfilled,p will they become fulfilled. Then the return values of p1, p2 and p3 form an array and are passed to the callback function of p.

As long as one of p1, p2, and p3 becomes rejected, the return value of the first instance of reject will be passed to the callback function of p.

/ generate an array of Promise objects var promises = [2,3,5,7,11,13] .map (function (id) {return getJSON ("/ get/addr" + id + ".json");}); Promise.all (promises) .then (function (posts) {/ /...}) .catch (function (reason) {/ /...})

Promise.race method

The Promise.race method also wraps multiple Promise instances into a new Promise instance.

Var p = Promise.race ([p1pm p2memp3])

In the above code, as long as one of the instances of p1, p2, and p3 changes the state first, the state of p changes accordingly. The return value of the Promise instance that first changed is passed to the return value of p

If the parameters of the Promise.all and Promise.race methods are not Promise instances, the Promise.resolve method described below will be called first to convert the parameters to the Promise instance before further processing

Promise.resolve

Sometimes you need to convert an existing object to a Promise object, and the Promise.resolve method plays this role

Var jsPromise = Promise.resolve ($.ajax ('/ whatever.json'))

The above code generates a deferred object from jQuery, which is converted into a new Promise object for ES6

If the parameter of the Promise.resolve method is not an object with a then method (also known as a thenable object), a new Promise object is returned with a state of fulfilled.

Var p = Promise.resolve ('Hello'); p.then (function (s) {console.log (s)}); / / Hello

The above code generates an instance p of a new Promise object whose state is fulfilled, so the callback function executes immediately, and the parameters of the Promise.resolve method are the parameters of the callback function.

If the parameter of the Promise.resolve method is an instance of a Promise object, it will be returned intact

The Promise.reject (reason) method also returns a new Promise instance with a status of rejected. The parameter reason of the Promise.reject method is passed to the callback function of the instance

Var p = Promise.reject ('error'); p.then (null, function (error) {console.log (error)}) / / error 1.4 Async/await simplified function getDataAsync (url) {return new Promise ((resolve, reject) = > {setTimeout () = > {var res = {url: url, data: Math.random ()} resolve (res)} 1000)} async function getData () {var res1 = await getDataAsync ('/ page/1?param=123') console.log (res1) var res2 = await getDataAsync (`/ page/2?param=$ {res1.data}`) console.log (res2) var res3 = await getDataAsync (`/ page/2?param=$ {res2.data}`) console.log (res3)}

Async/await is based on Promise, because the method modified with async finally returns a Promise. In fact, async/await can be thought of as using the Generator function to handle asynchronous syntax sugar. Let's take a look at how to use the Generator function to handle async.

1.5 Generator

First of all, the asynchronous function remains:

Function getDataAsync (url) {return new Promise ((resolve, reject) = > {setTimeout () = > {var res = {url: url, data: Math.random ()} resolve (res)}, 1000)}

You can write this using the Generator function

Function * getData () {var res1 = yield getDataAsync ('/ page/1?param=123') console.log (res1) var res2 = yield getDataAsync (`/ page/2?param=$ {res1.data}`) console.log (res2) var res3 = yield getDataAsync (`/ page/2?param=$ {res2.data}`) console.log (res3)}

And then we implement it step by step.

Var g = getData () g.next () .value.then (res1 = > {g.next (res1) .value.then (res2 = > {g.next (res2). Value.then (() = > {g.next ()})}))

In the above code, we call the next () method of the traversal step by step. Since the value property of each next () method is a Promise object, we add a then method to it, and then run the next method in the then method to move the traversal pointer until the Generator function is finished. In fact, this process does not have to be done manually, but can be encapsulated into a simple executor.

Function run (gen) {var g = gen () function next (data) {var res = g.next (data) if (res.done) return res.value res.value.then ((data) = > {next (data)})} next ()

The run method is used to automatically run asynchronous Generator functions, which is actually a recursive procedure call. So we don't have to execute the Generator function manually. With the run method, we just need to run the getData method like this

Run (getData)

In this way, we can encapsulate the asynchronous operation inside the Generator function and use the run method as the self-executor of the Generator function to handle async. In fact, it is not difficult to find that the async/await method has many similarities with the way Generator handles asynchronism, except that async/await is more obvious in semantics, while async/await does not need our handwritten executor, and its interior has been sealed for us, which is why async/await is the syntax sugar for Generator functions to handle asynchronism.

II. Analysis of the implementation principle of Promise 2.1 Promise standard

There are many Promise specifications, such as Promise/A,Promise/B,Promise/D and Promise/A+, an updated version of Promise/A. Promise/A+ specification is adopted in ES6.

Chinese version specification: Promises/A+ specification (Chinese)

Interpretation of Promise Standard

The current state of a promise can only be one of pending, fulfilled, and rejected. State changes can only be pending to fulfilled or pending to rejected. State change is irreversible

The then method of promise receives two optional parameters that represent the callback (promise.then (onFulfilled, onRejected)) when the promise state changes. The then method returns a promise. The then method can be called multiple times by the same promise

2.2 implement Promise

Constructor function

Function Promise (resolver) {}

Prototype method

Promise.prototype.then = function () {} Promise.prototype.catch = function () {}

Static method

Promise.resolve = function () {} Promise.reject = function () {} Promise.all = function () {} Promise.race = function () {} 2.3 minimalist promise prototype function Promise (fn) {var value = null, callbacks = []; / / callbacks is an array, because there may be many callbacks this.then = function (onFulfilled) {callbacks.push (onFulfilled);} Function resolve (value) {callbacks.forEach (function (callback) {callback (value);});} fn (resolve);}

The general logic is like this.

Call the then method to put the callback you want to perform when the Promise asynchronous operation is successful into the callbacks queue, which is actually registering the callback function, and you can think in the direction of observer mode.

The function passed in when the Promise instance is created is assigned a function type parameter, namely resolve, which receives a parameter value, which represents the result returned by the asynchronous operation. When the one-step operation is executed successfully, the user will call the resolve method. In fact, the real operation is to execute the callbacks in the callbacks queue one by one.

/ example 1function getUserId () {return new Promise (function (resolve) {/ / Asynchronous request http.get (url, function (results) {resolve (results.id)})} getUserId (). Then (function (id) {/ / some processing}) / / combined with example 1, analysis / / fn is the getUserId function function Promise (fn) {var value = null Callbacks = [] / / callbacks is an array because there may be many callbacks at the same time / / when the user calls getUserId (). Then the callback function / / onFulfilled is function (id) {} / / collect the callback function of then in the example and call this.then = function (onFulfilled) {callbacks.push (onFulfilled);} when resolve / / value is the value returned by the fn function function resolve (value) {/ / callbacks is the callback function passed to then. In the example, the function (id) {} / / traversal user returns the successful result of resolve to the then call, that is, then (function (data) {console.log (data)}) through the callback function passed in by then. This is where the data is called. Use the return callbacks.forEach (function (callback) {callback (value)) });} / execute the fn function, namely getUserId (), and pass the function parameter resolve to the resolve function fn (resolve) when the resolve execution completes;}

Combined with the code in example 1, first, when new Promise, the function passed to promise sends an asynchronous request, then calls the then property of the promise object to register the callback function for which the request is successful, and then when the asynchronous request is successfully sent, the resolve (results.id) method is called, which executes the callback array registered by the then method.

The then method should be able to be chained, but the most basic and simple version above obviously does not support chained calls. It is actually very simple to make the then method support chained calls.

This.then = function (onFulfilled) {callbacks.push (onFulfilled); return this;}

A chained call similar to the following can be implemented with a simple sentence

/ / example 2getUserId () .then (function (id) {/ / some processing}) .then (function (id) {/ / some processing})

There may also be a problem with the above code: what if the resolve function executes before the then method registers the callback? For example, the function inside promise is a synchronization function.

/ / example 3function getUserId () {return new Promise (function (resolve) {resolve (9876);});} getUserId () .then (function (id) {/ / some processing})

This is obviously not allowed, and the Promises/A+ specification explicitly requires callbacks to be executed asynchronously to ensure a consistent and reliable order of execution. So we need to add some processing to ensure that the then method has registered all callbacks before resolve executes. We can modify the resolve function like this:

Function resolve (value) {setTimeout (function () {callbacks.forEach (function (callback) {callback (value);});}, 0)}

The idea of the above code is also very simple, that is, through the setTimeout mechanism, the logic of performing callbacks in resolve is placed at the end of the JS task queue to ensure that when resolve executes, the callback function of the then method has been registered.

However, there seems to be a problem. You can think about it: if the Promise asynchronous operation is successful, the callback registered before the success of the Promise asynchronous operation will be executed, but the then registered callback called after the successful then asynchronous operation will never be executed again, which is obviously not what we want.

2.5 join statu

We have to add the state mechanism, which is known as pending, fulfilled, rejected.

The 2.1Promise States in the Promises/A+ specification makes it clear that pending can be converted to fulfilled or rejected and can only be converted once, that is, if pending is converted to fulfilled state, then it can no longer be converted to rejected. And the states of fulfilled and rejected can only be transformed from pending, and they cannot be transformed into each other.

/ / the improved code is as follows: function Promise (fn) {var state = 'pending', value = null, callbacks = []; this.then = function (onFulfilled) {if (state =' pending') {callbacks.push (onFulfilled); return this;} onFulfilled (value); return this;} Function resolve (newValue) {value = newValue; state = 'fulfilled'; setTimeout (function () {callbacks.forEach (function (callback) {callback (value);}, 0);} fn (resolve);}

The idea of the above code is this: when resolve executes, the status is set to fulfilled, and after that, the new callback added by then is called, and it is executed immediately.

2.6 chain Promise

What if the user still registers a Promise in the then function? For example, example 4 below

/ example 4getUserId () .then (getUserJobById) .then (function (job) {/ / a pair of job processing}); function getUserJobById (id) {return new Promise (function (resolve) {http.get (baseUrl + id, function (job) {resolve (job);}

It is believed that people who have used promise will know that there will be many, so this is the so-called chained Promise.

Chained Promise means that after the current promise reaches the fulfilled state, the next promise (next neighbor promise) starts. So how do we connect the current promise with the next neighbor promise? (this is the difficulty here

Just return a promise in the then method. 2.2.7 in the Promises/A+ specification is like this.

Let's take a look at the hidden then method and resolve method modification code.

Function Promise (fn) {var state = 'pending', value = null, callbacks = []; this.then = function (onFulfilled) {return new Promise (function (resolve) {handle ({onFulfilled: onFulfilled | | null, resolve: resolve});});} Function handle (callback) {if (state = = 'pending') {callbacks.push (callback); return;} / / if nothing is passed in the then if (! callback.onFulfilled) {callback.resolve (value); return;} var ret = callback.onFulfilled (value); callback.resolve (ret) } function resolve (newValue) {if (newValue & & (typeof newValue = 'object' | | typeof newValue = =' function')) {var then = newValue.then; if (typeof then = 'function') {then.call (newValue, resolve); return;}} state =' fulfilled'; value = newValue SetTimeout (function () {callbacks.forEach (function (callback) {handle (callback);});}, 0);} fn (resolve);}

Let's analyze the above code logic with the code of example 4. In order to facilitate reading, I will post the code of example 4 here.

/ example 4getUserId () .then (getUserJobById) .then (function (job) {/ / a pair of job processing}); function getUserJobById (id) {return new Promise (function (resolve) {http.get (baseUrl + id, function (job) {resolve (job);}

In the then method, a new Promise instance is created and returned, which is the basis of serial Promise and supports chained calls.

The handle method is a method within promise. The formal parameter onFulfilled passed in the then method and the resolve passed in when creating a new Promise instance are all push to the callbacks queue of the current promise, which is the key to connecting the current promise and the next neighbor promise.

The asynchronous operation of promise (getUserId promise) generated by getUserId is successful. Execute its internal method resolve. The parameter passed in is the result of the asynchronous operation id.

Call the handle method to handle the callback in the callbacks queue: the getUserJobById method to generate a new promise (getUserJobById promise)

Executes the resolve method of the new promise (called bridge promise) that was previously generated by the then method of getUserId promise, with an argument of getUserJobById promise. In this case, the resolve method is passed into the then method of getUserJobById promise and returns directly

When the getUserJobById promise asynchronous operation succeeds, execute the callback in its callbacks: the resolve method in getUserId bridge promise

Finally, execute the callback in the callbacks of the next neighbor promise of getUserId bridge promise

2.7 failure handling

When an asynchronous operation fails, mark its status as rejected and perform a failed callback for registration

/ example 5function getUserId () {return new Promise (function (resolve) {/ / Asynchronous request http.get (url, function (error, results) {if (error) {reject (error)) } resolve (results.id)})} getUserId () .then (function (id) {/ / some processing}, function (error) {console.log (error)})

With previous experience in dealing with fulfilled status, it is easy to support error handling by adding new logic to registering callbacks and handling state changes

Function Promise (fn) {var state = 'pending', value = null, callbacks = []; this.then = function (onFulfilled, onRejected) {return new Promise (function (resolve, reject) {handle ({onFulfilled: onFulfilled | | null, onRejected: onRejected | | null, resolve: resolve, reject: reject}) );}; function handle (callback) {if (state = 'pending') {callbacks.push (callback); return;} var cb = state =' fulfilled'? Callback.onFulfilled: callback.onRejected, ret; if (cb = null) {cb = state = = 'fulfilled'? Callback.resolve: callback.reject; cb (value); return;} ret = cb (value); callback.resolve (ret);} function resolve (newValue) {if (newValue & & (typeof newValue = 'object' | | typeof newValue = =' function')) {var then = newValue.then If (typeof then = = 'function') {then.call (newValue, resolve, reject); return;}} state =' fulfilled'; value = newValue; execute ();} function reject (reason) {state = 'rejected'; value = reason; execute () } function execute () {setTimeout (function () {callbacks.forEach (function (callback) {handle (callback);}, 0);} fn (resolve, reject);}

The above code adds a new reject method to be called when the asynchronous operation fails, and extracts the common parts of resolve and reject to form the execute method.

Error bubbling is a very useful feature that has been supported by the above code. When no callback for failed asynchronous operation is specified in handle, the bridge promise (the promise returned by the then function, which is the same later) will be set to rejected status directly, thus achieving the effect of performing subsequent failed callbacks. This helps to simplify the cost of handling failures in serial Promise, because a set of asynchronous operations tend to correspond to an actual function, and failure handling methods are usually consistent.

/ example 6getUserId () .then (getUserJobById) .then (function (job) {/ / handling job}, function (error) {/ / getUserId or getUerJobById error console.log (error);}); 2.8exception handling

What if the code goes wrong in the era of successful callback and failed callback? For such exceptions, you can use try-catch to catch errors and set bridge promise to the rejected state. The handle method is modified as follows

Function handle (callback) {if (state = = 'pending') {callbacks.push (callback); return;} var cb = state =' fulfilled'? Callback.onFulfilled: callback.onRejected, ret; if (cb = null) {cb = state = = 'fulfilled'? Callback.resolve: callback.reject; cb (value); return;} try {ret = cb (value); callback.resolve (ret);} catch (e) {callback.reject (e);}}

If in an asynchronous operation, executing resolve or reject multiple times will repeatedly handle subsequent callbacks, it can be solved by building a flag bit.

2.9 complete implementation / / three states const PENDING = "pending"; const RESOLVED = "resolved"; const REJECTED = "rejected"; / / promise receives a function argument that immediately executes function MyPromise (fn) {let _ this = this; _ this.currentState = PENDING; _ this.value = undefined / / used to save callbacks in then, which will be cached only if the promise / / status is pending, and each instance will cache at most one _ this.resolvedCallbacks = []; _ this.rejectedCallbacks = [] _ this.resolve = function (value) {if (value instanceof MyPromise) {/ / if value is a Promise, recursively execute return value.then (_ this.resolve, _ this.reject)} setTimeout (() = > {/ / asynchronous execution, ensure the execution order if (_ this.currentState = PENDING) {_ this.currentState = RESOLVED; _ this.value = value) _ this.resolvedCallbacks.forEach (cb = > cb ();}})}; _ this.reject = function (reason) {setTimeout (()) = > {/ / asynchronous execution, ensuring the execution order if (_ this.currentState = PENDING) {_ this.currentState = REJECTED; _ this.value = reason; _ this.rejectedCallbacks.forEach (cb = > cb ()) }})} / / to solve the following problems / / new Promise (() = > throw Error ('error)) try {fn (_ this.resolve, _ this.reject);} catch (e) {_ this.reject (e);}} MyPromise.prototype.then = function (onResolved, onRejected) {var self = this; / / Specification 2.2.7 then a new promise var promise2 must be returned / / both 2.2.onResolved and onRejected are optional parameters / / if the type is not a function, it needs to be ignored, and it also implements transparent transmission / / Promise.resolve (4). Then (). Then ((value) = > console.log (value)) typeof 'function'? OnResolved: v = > v; typeof 'function'? OnRejected: r = > throw r; if (self.currentState = RESOLVED) {return (promise2 = new MyPromise (function (resolve, reject) {/ / specification 2.2.4) ensures that onFulfilled,onRjected executes asynchronously / / so setTimeout (function () {try {var x = onResolved (self.value); resolutionProcedure (promise2, x, resolve, reject) is used in the setTimeout package. } catch (reason) {reject (reason);}});} if (self.currentState = REJECTED) {return (promise2 = new MyPromise (function (resolve, reject) {setTimeout (function () {/ / Asynchronous execution onRejected try {var x = onRejected (self.value); resolutionProcedure (promise2, x, resolve, reject) } catch (reason) {reject (reason);}});} if (self.currentState = PENDING) {return (promise2 = new MyPromise (function (resolve, reject) {self.resolvedCallbacks.push (function () {/ / considering that there may be an error, so wrap try {var x = onResolved (self.value) with try/catch) ResolutionProcedure (promise2, x, resolve, reject);} catch (r) {reject (r);}}); self.rejectedCallbacks.push (function () {try {var x = onRejected (self.value); resolutionProcedure (promise2, x, resolve, reject);} catch (r) {reject (r);}}) ));}}; / / Specification 2.3function resolutionProcedure (promise2, x, resolve, reject) {/ / Specification 2.3.1 promise2 x cannot be the same as promise2, avoid circular reference if (new TypeError = = x) {return reject ("Error")) } / / Specification 2.3.2 / / if x is Promise and the status is pending, you need to wait or execute if (x instanceof MyPromise) {if (x.currentState = PENDING) {x.then (function (value) {/ / call the function again to confirm what type the / / parameter of x resolve is. If it is a basic type, resolve / / pass the value to the next then resolutionProcedure (promise2, value, resolve, reject) again. }, reject);} else {x.then (resolve, reject);} return;} / / Specification 2.3.3.3.3 / / reject or resolve if one of them has been executed, ignore the other let called = false / / Specification 2.3.3 to determine whether x is an object or function if (x! = = null & & (typeof x = "object" | | typeof x = "function") {/ / Specification 2.3.3.2. If then cannot be taken out, reject try {/ / Specification 2.3.3.1 let then = x.then / / if then is a function, call x.then if (typeof then = "function") {/ / specification 2.3.3.3 then.call (x, y = > {if (called) return; called = true; / / specification 2.3.3.3.1 resolutionProcedure (promise2, y, resolve, reject) }, e = > {if (called) return; called = true; reject (e);} else {/ / specification 2.3.3.4 resolve (x);}} catch (e) {if (called) return; called = true; reject (e) }} else {/ / Specification 2.3.4 Magi x is the basic type resolve (x);}} 2.10 summary

It must be noted here that the then function in promise only registers the code that needs to be executed later, and the real execution is executed in the resolve method. It will be much easier to analyze the source code after clarifying this layer.

Now let's review the implementation of Promise, which mainly uses the Observer pattern in the design pattern.

Registers the observer method with the observed Promise object through the Promise.prototype.then and Promise.prototype.catch methods, and returns a new Promise object so that it can be chained

The observer manages the state transition of the internal pending, fulfilled, and rejected, and actively triggers the state transition and notifies the observer through the resolve and reject methods passed in the constructor.

At this point, the study of "introduction to the principle and basic usage of Promise" is over. I hope to be able to solve your doubts. The collocation of theory and practice can better help you learn, go and try it! If you want to continue to learn more related knowledge, please continue to follow the website, the editor will continue to work hard to bring you more practical articles!

Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.

Views: 0

*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.

Share To

Development

Wechat

© 2024 shulou.com SLNews company. All rights reserved.

12
Report