Книга: Angular 2 Cookbook
Назад: 4. Mastering Promises
Дальше: Chaining Promises and Promise handlers

Understanding and implementing basic Promises

Promises are very useful in many of the core aspects of Angular. Although they are no longer bound to the core framework service, they still manifest themselves throughout Angular's APIs. The implementation is considerably simpler than Angular 1, but the main rhythms have remained consistent.

Note

You can refer to the code, links, and a live example of this at .

Getting ready

Before you start using promises, you should first understand the problem they are trying to solve. Without worrying too much about the internals, you can classify the concept of a Promise into three distinct stages:

  • Initialization: I have a piece of work that I want to accomplish, and I want to define what should happen when this work is completed. I do not know whether this work will be ever completed; also, the work may either fail or succeed.
  • Pending: I have started the work, but it has not been completed yet.
  • Completed: The work is finished, and the promise assumes a final state. The "completed" state assumes two forms: resolved and rejected. These correspond to success and failure, respectively.

There is more nuance to how promises work, but for now, this is sufficient to get into some of the code.

How to do it...

A promise implementation in one of its simplest forms is as follows:

// promises are instantiated with the 'new' keyword   var promise = new Promise(() => {});   

The function passed to the Promise constructor is the piece of work that is expected to execute asynchronously. The formal term for this function is executor.

Note

The Promise constructor doesn't care at all about how the executor function behaves. It merely provides it with the two resolve and reject functions. It is left up to the executor function to utilize them appropriately. Note that the executor function doesn't need to be asynchronous at all; however, if it isn't asynchronous, then you might not need a Promise for what you are trying to accomplish.

When this function is executed, internally it understands when it is completed; however, on the outside, there is no construct that represents the concept of "run this when the executor function is completed". Therefore, its first two parameters are the resolve and reject functions. The promise wrapping the executor function is in the pending state until one of these is invoked. Once invoked, the promise irreversibly assumes the respective state.

Note

The executor function is invoked immediately when the promise is instantiated. Just as importantly, it is invoked before the promise instantiation is returned. This means that if the promise reaches either a fulfilled or rejected state inside the executor synchronously, then the return value of new Promise(...) will be the freshly constructed Promise with a resolved or rejected status, skipping the pending state entirely.

The return value of executor is unimportant. No matter what it returns, the promise constructor will always return the freshly created promise.

The following code demonstrates five different examples of ways that a promise can be instantiated, resolved, or rejected:

// This executor is passed resolve and reject, but is    // effectively a no-op, so the promise p2 will forever    // remain in the 'pending' state.   const p1 = new Promise((resolve, reject) => {});      // This executor invokes 'resolve' immediately, so   // p2 will transition directly to the 'fulfilled' state.   const p2 = new Promise((resolve, reject) => resolve());      // This executor invokes 'reject' immediately, so   // p3 will transition directly to the 'rejected' state.   // A transition to the 'rejected' state will also throw   // an exception. This exception is thrown after the    // executor completes, so any logic following the    // invocation of reject will still be executed.   const p3 = new Promise((resolve, reject) => {     reject();     // This log() prints before the exception is thrown     console.log('I got rejected!');    });      // This executor invokes 'resolve' immediately, so   // p4 will transition directly to the 'fulfilled' state.   // Once a promise exits the 'pending' state, it cannot change   // again, so even though reject is invoked afterwards, the   // final state of p4 is still 'fulfilled'.   const p4 = new Promise((resolve, reject) => {     resolve();     reject();   });      // This executor assigns its resolve function to a variable   // in the encompassing lexical scope so it can be called   // outside the promise definition.   var outerResolve;   const p5 = new Promise((resolve, reject) => {     outerResolve = resolve();   });   // State of p5 is 'pending'      outerResolve();   // State of p5 is 'fulfilled'   

With what you've done so far, you will not find the promise construct to be of much use; this is because all that the preceding code accomplishes is the setting up of the state of a single promise. The real value emerges when you set the subsequent state handlers. A Promise object's API exposes a then() method, which allows you to set handlers to be executed when the Promise reaches its final state:

// p1 is a simple promise to which you can attach handlers   const p1 = new Promise((resolve, reject) => {});      // p1 exposes a then() method which accepts a    // resolve handler (onFulfilled), and a    // reject handler (onRejected)   p1.then(     // onFulfilled is invoked when resolve() is invoked     () => {},     // onRejected is invoke when reject() is invoked     () => {});   // If left here, p1 will forever remain "pending"         // Using the 'new' keyword still allows you to call a   // method on the returned instance, so defining the   // then() handlers immediately is allowed.   //   // Instantly resolves p2   const p2 = new Promise((resolve, reject) => resolve())     .then(       // This method will immediately be invoked following       // the p2 executor invoking resolve()       () => console.log('resolved!'));   // "resolved!"         // Instantly rejects p3   const p3 = new Promise((resolve, reject) => reject())     .then(       () => console.log('resolved!'),       // This second method will immediately be invoked following       // the p3 executor invoking reject()       () => console.log('rejected!'));   // "rejected!"         const p4 = new Promise((resolve, reject) => reject())     // If you don't require use of the resolve handler,     // catch() allows you to define just the error handling     .catch(() => console.log('rejected!'));         // executor parameters can be captured outside its lexical   // scope for later invocation   var outerResolve;   const p5 = new Promise((resolve, reject) => {     outerResolve = resolve;   }).then(() => console.log('resolved!'));      outerResolve();   // "resolved!"   

How it works...

Promises in JavaScript confer to the developer the ability to write asynchronous code in parallel with synchronous code more easily. In JavaScript, this was formerly solved with nested callbacks, colloquially referred to as "callback hell." A single callback-oriented function might be written as follows:

// a generic asynchronous callback function   function asyncFunction(data, successCallback, errorCallback) {     // asyncFunction will perform some operation that may succeed,     // may fail, or may not return at all, any of which     // occurs in an unknown amount of time        // this pseudo-response contains a success boolean,     // and the returned data if successful     asyncOperation(data, function(response) {       if (response.success === true) {         successCallback(response.data);       } else {         errorCallback();       }     });   };   

If your application does not demand any semblance of in-order or collective completion, then the following will suffice:

function successCallback(data) {     // asyncFunction succeeded, handle data appropriately   };   function errorCallback() {     // asyncFunction failed, handle appropriately   };      asyncFunction(data1, successCallback, errorCallback);   asyncFunction(data2, successCallback, errorCallback);   asyncFunction(data3, successCallback, errorCallback);   

This is almost never the case though. Often, your application will either demand that this data is acquired in a sequence, or that an operation that requires multiple asynchronously acquired pieces of data executes once all of the data has been successfully acquired. In this case, without access to promises, the callback hell emerges:

asyncFunction(data1, (foo) => {     asyncFunction(data2, (bar) => {       asyncFunction(data3, (baz) => {         // foo, bar, baz can now all be used together         combinatoricFunction(foo, bar, baz);       }, errorCallback);     }, errorCallback);   }, errorCallback);   

This so-called callback hell here is really just an attempt to serialize three asynchronous calls, but the parametric topology of these asynchronous functions forces the developer to subject their application to this ugliness.

There's more...

An important point to remember about promises is that they allow you to break apart a calculation into two parts: the part that understands when the promise's "execution" has been completed and the part that signals to the rest of the program that the execution has been completed.

Decoupled and duplicated Promise control

Because a promise can give away the control of who decides where the Promise will be made ready, multiple foreign parts of the code can set the state of the promise.

A promise instance can be either resolved or rejected at multiple places inside the executor::

const p = new Promise((resolve, reject) => {     // the following are pseudo-methods, each of which can be called      // independently and asynchronously, or not at all     function canHappenFirst() { resolve(); };     function mayHappenFirst() { resolve(); }     function mightHappenFirst() { reject(); };   });    

A promise instance can also be resolved at multiple places outside the executor:

var outerResolve;   const p = new Promise((resolve, reject) => {     outerResolve = resolve;   });       // the following are pseudo-methods, each of which can be called    // independently and asynchronously, or not at all   function canHappenFirst() { outerResolve (); };   function mayHappenFirst() { outerResolve (); }   function mightHappenFirst() { outerResolve (); };   

Note

Once a Promise's state becomes fulfilled or rejected, attempts to reject or resolve that promise further will be silently ignored. A promise state transition occurs only once, and it cannot be altered or reversed.

Resolving a Promise to a value

Part of the central concept of promise constructs is that they are able to "promise" that there will be a value available when the promise is resolved.

States do not necessarily have a data value associated with them; they only confer to the promise a defined state of evaluation:

var resolveHandler = () => {},        rejectHandler = () => {};   const p0 = new Promise((resolve, reject) => {     // state can be defined with any of the following:     // resolve();     // reject();     // resolve(myData);     // reject(myData);   }).then(resolveHandler, rejectHandler);   

An evaluated promise (resolved or rejected) is associated with a handler for each of the states. This handler is invoked upon the promise's transition into that respective state. These handlers can access the data returned by the resolution or rejection:

const p1 = new Promise((resolve, reject) => {     // console.info is the resolve handler,     // console.error is the reject handler          resolve(123);   }).then(console.info, console.error);       // (info) 123      // reset to demonstrate reject()   const p2 = new Promise((resolve, reject) => {     // console.info is the resolve handler,     // console.error is the reject handler          reject(456);   }).then(console.info, console.error);       // (error) 456   

Delayed handler definition

Unlike callbacks, handlers can be defined at any point in the promise life cycle, including after the promise state has been defined:

const p3 = new Promise((resolve, reject) => {     // immediately resolve the promise     resolve(123);   });      // subsequently define a handler, will be immediately   // invoked since promise is already resolved    p3.then(console.info);      // (info) 123   

Multiple handler definition

Similar to how a single deferred object can be resolved or rejected at multiple places in the application, a single promise can have multiple handlers that can be bound to a single state. For example, a single promise with multiple resolved handlers attached to it will invoke all the handlers if the resolved state is reached; the same is true for rejected handlers:

const p4 = new Promise((resolve, reject) => {     // Invoke resolve() after 1 second     setTimeout(() => resolve(), 1000);   });      const cb = () => console.log('called');      p4.then(cb);   p4.then(cb);      // After 1 second:   // "called"   // "called"   

Private Promise members

An extremely important departure from Angular 1 is that the state of a promise is totally opaque to the execution. Formerly, you were able to tease out the state of the promise using the pseudo-private $$state property. With the formal ES6 Promise implementation, the state cannot be inspected by your application. You can, however, glean the state of a promise from the console. For example, inspecting a promise in Google Chrome yields something like the following:

Promise {     [[PromiseStatus]]: "fulfilled",      [[PromiseValue]]: 123   }   

Note

PromiseStatus and PromiseValue are private Symbols, which are a new construct in ES6. Symbol can be thought of as a unique key that is useful for setting properties on objects that shouldn't be easily accessed from elsewhere. For example, if a promise were to use the 'PromiseStatus' string to key a property, it could be easily used outside the object, even if the property was supposed to remain private. With ES6 private symbols, however, a symbol is unique when generated, and there is no good way to access it inside the instance.

See also

  • Chaining Promises and Promise handlers details how you can wield this powerful chaining construct to serialize asynchronous operations
  • Creating Promise wrappers with Promise.resolve() and Promise.reject() demonstrates how to use the core Promise utilities
Назад: 4. Mastering Promises
Дальше: Chaining Promises and Promise handlers

thank you
Flame
cant read the code since it is all on a single line. Also this comments section is russian
Rakuneque
DATA COLLECTION AND ANALYSIS Two reviewers extracted data and assessed methodological quality independently lasix torsemide conversion Many others were in that space already