For readers of this article

This article is written for those of you who have some experience with promises. If you haven’t used promises yet, this article may not be for you

Interpretation of Promise Standard

  1. There is only one then method, no catch, race, all, etc., not even a constructor

    The Promise standard only specifies the behavior of the then method of the Promise object. All other common methods/functions are not specified, including catch, race, all and other commonly used methods, nor even how to construct a Promise object. In addition, then does not have a third argument supported by common implementations (Q, $Q, etc.), usually called onProgress

  2. The then method returns a new Promise

    The Promise’s then method returns a new Promise instead of returning this, which is explained more below

    promise2 = promise1.then(alert) promise2 ! = promise1 // trueCopy the code
  3. Different Promise implementations need to be interoperable

  4. The initial state of a Promise is pending, which can be changed to a pity (this state is called resolved for consistency in this paper) or Rejected. Once the state is determined, it cannot be changed to another state again. The process of state determination is called settle

  5. See here for more specific criteria

Make a Promise one step at a time

Let’s implement a Promise step by step

The constructor

Since the standard does not specify how to construct a Promise object, we will also construct a Promise object in the same way that is currently common in general Promise implementations, which is used in ES6 native Promises:

// The Promise constructor receives an executor function, and after the executor function has performed a synchronous or asynchronous operation, Resolve and reject var promise = new promise (function(resolve, reject) {/* If the operation is successful, call resolve and pass value. Call reject and pass reason */})Copy the code

The framework for implementing the constructor is as follows:

Function Promise(executor) {var self = this self. Status = 'pending' // Promise current status self.data = undefined // Promise value Self.onresolvedcallback = [] Since more than one callback may be added to a Promise before it ends, self.onrejectedCallback = [] // The set of callback functions used in Promise reject, Executor (resolve, reject) // Execute executor and pass in arguments}Copy the code

The code above basically implements the body of the Promise constructor, but there are two problems:

  1. We passed the executor function two parameters, resolve and reject, which are not yet defined

  2. The executor can throw something like this, and if the executor fails, the Promise should be thrown with the value reject:

    new Promise(function(resolve, reject) {
      throw 2
    })Copy the code

So we need to define resolve and reject in the constructor:

Function Promise(executor) {var self = this self. Status = 'pending' // Promise current status self.data = undefined // Promise value Self.onresolvedcallback = [] Since more than one callback may be added to a Promise before it ends, self.onrejectedCallback = [] // The set of callback functions used in Promise reject, Function resolve(value) {// TODO} function reject(reason) {// TODO} try {// To allow for the possibility that something could go wrong when executing an executor, we wrap a try/catch block around it. Discard the Promise executor(resolve, reject) // Execute executor} catch(e) {reject(e)}}Copy the code

Can resolve and reject not be defined in constructors, one might ask? Considering that we call both functions in the executor function as resolve(value), reject(reason), and not as resolve. Call (promise, value), Call (reject. Call (promise, reason)), so there must also be an implicit this inside the call, which means that either the functions have been bind to the Executor, or they are defined inside the constructor. Use self to access the owning Promise object. So if we wanted to define these two functions outside the constructor, we could indeed write:

function resolve() {
  // TODO
}
function reject() {
  // TODO
}
function Promise(executor) {
  try {
    executor(resolve.bind(this), reject.bind(this))
  } catch(e) {
    reject.bind(this)(e)
  }
}Copy the code

However, bind returns a new function as well, so each Promise object has its own pair of resolve and reject functions, just like inside the constructor, so we define them directly inside the constructor. However, if the browser is optimized for BIND, using the latter form should improve memory efficiency.

In addition, our implementation does not consider hiding variables on this, which makes it possible for the state of the Promise to be changed outside the executor function. In a reliable implementation, the state and end result of the constructed Promise object should not be changed from outside.

Next, we implement the resolve and reject functions

function Promise(executor) {
  // ...

  function resolve(value) {
    if (self.status === 'pending') {
      self.status = 'resolved'
      self.data = value
      for(var i = 0; i < self.onResolvedCallback.length; i++) {
        self.onResolvedCallback[i](value)
      }
    }
  }

  function reject(reason) {
    if (self.status === 'pending') {
      self.status = 'rejected'
      self.data = reason
      for(var i = 0; i < self.onRejectedCallback.length; i++) {
        self.onRejectedCallback[i](reason)
      }
    }
  }

  // ...
}Copy the code

It basically changes the state to the corresponding value, stores the corresponding value and reason on the data attribute of self, and then executes the corresponding callback function. The logic is very simple and there is not much explanation here.

thenmethods

The Promise object has a THEN method that registers callbacks after the Promise state is determined, and obviously the THEN method needs to be written on the prototype chain. The THEN method returns A Promise. The Promise/A+ standard does not require that the Promise returned be A new object, but the Promise/A standard explicitly states that then returns A new object, In current Promise implementations, then almost always returns a new Promise(detail) object, so in our implementation, then also returns a new Promise object.

I think there’s a little bit of a contradiction in the standard:

If onResolved/onRejected in (onResolved, onRejected) returns a Promise, then promise2 will take the state and value of the Promise. But consider this code:

promise2 = promise1.then(function foo(value) {
  return Promise.reject(3)
})Copy the code

If foo is running, the state of promise1 is resolved. If this is returned by then, promise2 and promise1 are the same object. In this case, the state of promise.reject (3) has been determined, and there is no way to take the state and result of promise.reject (3) as your own, because once the Promise state is determined, it cannot be changed to another state.

In addition, each Promise object can call the THEN method multiple times, and the state of the Promise returned by each call to then depends on the return value of the parameter passed in that call to THEN. Therefore, THEN cannot return this, because the Promise returned by then may be different each time.

Let’s implement the then method:

// the then method accepts two arguments, onResolved, onRejected, Promise.prototype. Then = function(onResolved, onRejected) {var self = this var promise.prototype = function(onResolved, onRejected) If the then argument is not function, we need to ignore it and handle onResolved = typeof onResolved === 'function'? onResolved : function(v) {} onRejected = typeof onRejected === 'function' ? onRejected : function(r) {} if (self.status === 'resolved') { return promise2 = new Promise(function(resolve, reject) { }) } if (self.status === 'rejected') { return promise2 = new Promise(function(resolve, reject) { }) } if (self.status === 'pending') { return promise2 = new Promise(function(resolve, reject) { }) } }Copy the code

There are three possible states for a Promise, which we deal with in three if blocks, each of which returns a New Promise.

As the standard tells us, the value of promise2 depends on the return value of the function in then:

promise2 = promise1.then(function(value) {
  return 4
}, function(reason) {
  throw new Error('sth went wrong')
})Copy the code

If promise1 is resolved, promise2 will be resolved. If promise1 is rejected, promise2 will be rejected by new Error(‘ STH went wrong’) reject.

So we need to implement onResolved or onRejected in then and use the return value (x) to determine the outcome of promise2. If onResolved/onRejected returns a Promise, Promise2 directly takes the result of this Promise:

Promise.prototype. Then = function(onResolved, onRejected) {var self = this var promise.prototype = function(onResolved, onRejected) OnResolved = typeof onResolved === 'function'? onResolved : function(value) {} onRejected = typeof onRejected === 'function' ? onRejected : Function (reason) {} if (self.status === 'resolved') {// We called onResolved // because there's a possibility of throwing, Return promise2 = new Promise(function(resolve, Reject) {try {var x = onResolved(self.data) if (x instanceof Promise) { X. resolve(resolve, reject)} resolve(x)} catch (e) {reject(e) {// If there is an error, {// If (onRejected) {// If (onRejected) {// If (onRejected) {// If (onRejected) { If (self.status === 'rejected') {return promise2 = new Promise(function(resolve, reject) { try { var x = onRejected(self.data) if (x instanceof Promise) { x.then(resolve, Reject)}} Catch (e) {reject(e)}})} if (self.status === 'pending') {// If the current Promise is still pending, We can't decide onResolved or onRejected. We can only do this after the Promise state is determined. // So we need to put our ** 2 case ** processing logic as callback in the promise1 callback array (in this case this/self) Return promise2 = new Promise(function(resolve, reject) { self.onResolvedCallback.push(function(value) { try { var x = onResolved(self.data) if (x instanceof Promise) {  x.then(resolve, reject) } } catch (e) { reject(e) } }) self.onRejectedCallback.push(function(reason) { try { var x = onRejected(self.data) if (x instanceof Promise) { x.then(resolve, Reject)}} the catch (e) {reject (e)}}}})}) / / in order to convenient below, Prototype. Catch = function(onRejected) {return this. Then (null, onRejected)}Copy the code

So far, we’ve basically implemented everything in the Promise standard, but there are a few issues:

  1. There needs to be seamless interaction between different Promise implementations, that is, between the Promise of Q, the Promise of ES6, and the Promise we implement, and other Promise implementations, that should and should call each other seamlessly, such as:

    New MyPromise(function(resolve, Reject) {// We implement a Promise setTimeout(function() {resolve(42)}, Then (function() {return new Promise. Reject (2) // ES6 Promise}). Then (function() {return q.a. ([// Q Promise) New MyPromise(resolve=>resolve(8)), // we implement new Promise. Resolve (9), // ES6 Promise q.resolve (9) // Q Promise])})Copy the code

    The code we implemented earlier did not deal with such logic. We only judged whether the return value of onResolved/onRejected is the instance of our Promise and did not make any other judgment. Therefore, the code above cannot run correctly in our Promise at present.

  2. There is no way to handle the following code:

    new Promise(resolve=>resolve(8))
      .then()
      .then()
      .then(function foo(value) {
        alert(value)
      })Copy the code

    The correct behavior would be to alert 8, and if we took our Promise and ran the code above, it would alert undefined. This behavior is called penetration, meaning that the value of 8 will pass through both THEN (Promise is more accurate) to foo in the last THEN, become its argument, and eventually alert 8.

Let’s first deal with the simple case of value penetration

Penetration of the Promise value

If you look at it, you’ll see that we want the following code

new Promise(resolve=>resolve(8))
  .then()
  .catch()
  .then(function(value) {
    alert(value)
  })Copy the code

The behavior of this code is the same

new Promise(resolve=>resolve(8))
  .then(function(value){
    return value
  })
  .catch(function(reason){
    throw reason
  })
  .then(function(value) {
    alert(value)
  })Copy the code

Function (value) {return value} and function(reason) {throw reason} are the default values for the two arguments to then. So we just need to change the part of “onResolved and onRejected” in then to the following:

onResolved = typeof onResolved === 'function' ? onResolved : function(value) {return value}
onRejected = typeof onRejected === 'function' ? onRejected : function(reason) {throw reason}Copy the code

So Promise’s magic value penetration isn’t so black magic, it’s just the then default argument that passes the value back or throws it

Interactions between different promises

As for the interaction between different promises, the standard specifies how to use the value returned by the arguments of then to determine the state of promise2. All we need to do is translate the standard into code.

Here’s a quick explanation of the criteria:

So we’re going to treat the onResolved/onRejected return value X as a possible Promise object and call the THEN method on X in the safest way. If we do this, Then different promises can interact with each other. While the standard to be on the safe side, even if x returned with a then properties but it doesn’t follow the Promise of the standard objects (such as the two parameters in this x, then it is called, synchronous or asynchronous invocation (PS, in principle, then the two parameters need to be asynchronous calls, talk about below), or the wrong call them again, Or then is not a function at all), can be handled as correctly as possible.

I think the reason why different Promise implementations need to be able to interact with each other is obvious. Promises are not a standard from the beginning of JS, and different third-party implementations don’t know each other. If you use a library that encapsulates a Promise implementation, Imagine if it didn’t interact with your own Promise implementation…

I encourage you to read the following code against the standard, which is quite detailed, so you should be able to find similar code in any Promise implementation:

/ * resolvePromise function is decided according to the value of x promise2 state function is also the standard of [Promise Resolution Procedure] (https://promisesaplus.com/#point-47) X is' promise2 = promise1. Then (onResolved, The 'resolve' and 'reject' returns in 'onRejected' and 'resolved' are the 'executor' arguments of 'promise2' that are difficult to mount anywhere else. I believe that you can compare the standard to the standard into the code, here only mark the corresponding position of the code in the standard, */ function resolvePromise(promise2, x, resolve, Reject) {var then var thenCalledOrThrow = false if (promise2 === x) {// Corresponding to section 2.3.1 Return Reject (new TypeError('Chaining cycle detected for promise! '))} if (x instanceof Promise) {if (x instanceof Promise) {if (x instanceof Promise) { Resolve if (x.status === 'pending') {function(value) {resolvePromise(promise2, value, Resolve, reject)}, reject)} else {// But if the Promise's status is already established, it must have a "normal" value instead of a thenable, Resolve (reject)} return} if ((x! = = null) && (typeof (x = = = 'object') | | (typeof x = = = 'function'))) {/ / 2.3.3 try {/ / 2.3.3.1 because x.t hen is likely to be a getter, In this case, multiple reads can have the side effect of determining its type and calling it, If (typeof then === 'function') {// 2.3.3.3 then.call(x, Function rs(y) {// 2.3.3.3.1 if (thenCalledOrThrow) return thenCalledOrThrow = true Return resolvePromise(promise2, y, resolve, reject) Function rj(r) {// if (thenCalledOrThrow) return thenCalledOrThrow = true Reject (r)})} else {// 2.3.3.4 resolve(x)}} catch (e) {// 2.3.3.2 if (thenCalledOrThrow) return // 2.3.3.3.3 ThenCalledOrThrow = true return reject(e)}} else {// 2.3.4 resolve(x)}}Copy the code

We then use the call to this function to replace several places in then that determine whether x is a Promise object, as shown in the full code below.

In principle, the promise. Then (onResolved, onRejected) function should be called asynchronously.

In practice, this requirement ensures that onFulfilled and onRejected execute asynchronously, after the event loop turn in which then is called, and with a fresh stack.

So we need to make a little change to our code, which is to add setTimeout(fn, 0) in four places, this will be commented in the full code, please find out for yourself.

In fact, even if you don’t follow the standards, you will eventually discover when you test yourself that if the arguments of then aren’t called asynchronously, there will be cases where promises don’t behave as expected. By constantly testing yourself, you will inevitably end up making the arguments of THEN execute asynchronously, making the Executor function execute immediately. I did not refer to the standard when I realized the Promise at the beginning, but tested by experience and finally found this problem.

At this point, we have implemented a Promise, the complete code is as follows:

try { module.exports = Promise } catch (e) {} function Promise(executor) { var self = this self.status = 'pending' self.onResolvedCallback = [] self.onRejectedCallback = [] function resolve(value) { if (value instanceof Promise) { return value.then(resolve, Reject)} setTimeout(function() {// Execute all callback functions asynchronously if (self.status === 'pending') {self.status = 'resolved' self.data = value for (var i = 0; i < self.onResolvedCallback.length; I ++) {self.onresolvedCallback [I](value)}}})} function reject(reason) {setTimeout(function() {// Execute all callbacks asynchronously if (self.status === 'pending') { self.status = 'rejected' self.data = reason for (var i = 0; i < self.onRejectedCallback.length; i++) { self.onRejectedCallback[i](reason) } } }) } try { executor(resolve, reject) } catch (reason) { reject(reason) } } function resolvePromise(promise2, x, resolve, reject) { var then var thenCalledOrThrow = false if (promise2 === x) { return reject(new TypeError('Chaining cycle detected for promise! ')) } if (x instanceof Promise) { if (x.status === 'pending') { //because x could resolved by a Promise Object x.then(function(v) { resolvePromise(promise2, v, resolve, reject) }, reject) } else { //but if it is resolved, it will never resolved by a Promise Object but a static value; x.then(resolve, reject) } return } if ((x ! == null) && ((typeof x === 'object') || (typeof x === 'function'))) { try { then = x.then //because x.then could be a getter if (typeof then === 'function') { then.call(x, function rs(y) { if (thenCalledOrThrow) return thenCalledOrThrow = true return resolvePromise(promise2, y, resolve, reject) }, function rj(r) { if (thenCalledOrThrow) return thenCalledOrThrow = true return reject(r) }) } else { resolve(x) } } catch (e) { if (thenCalledOrThrow) return thenCalledOrThrow = true return reject(e) } } else { resolve(x) } } Promise.prototype.then = function(onResolved, onRejected) { var self = this var promise2 onResolved = typeof onResolved === 'function' ? onResolved : function(v) { return v } onRejected = typeof onRejected === 'function' ? onRejected : function(r) { throw r } if (self.status === 'resolved') { return promise2 = new Promise(function(resolve, Reject) {setTimeout (function () {/ / asynchronous execution onResolved try {var x = onResolved (self. Data) resolvePromise (promise2, x, resolve, reject) } catch (reason) { reject(reason) } }) }) } if (self.status === 'rejected') { return promise2 = new Promise(function(resolve, Reject) {setTimeout(function() {var x = onRejected(self.data) resolvePromise(promise1, x, Resolve, reject)} catch (reason) {reject(reason)}})} if (self.status === 'pending') { Because these functions are necessarily called by resolve or Reject, which are already executed asynchronously, Return promise2 = new Promise(function(resolve, reject) { self.onResolvedCallback.push(function(value) { try { var x = onResolved(value) resolvePromise(promise2, x, resolve, reject) } catch (r) { reject(r) } }) self.onRejectedCallback.push(function(reason) { try { var x = onRejected(reason) resolvePromise(promise2, x, resolve, reject) } catch (r) { reject(r) } }) }) } } Promise.prototype.catch = function(onRejected) { return this.then(null, onRejected) } Promise.deferred = Promise.defer = function() { var dfd = {} dfd.promise = new Promise(function(resolve, reject) { dfd.resolve = resolve dfd.reject = reject }) return dfd }Copy the code

test

How do we make sure that the Promise we implement meets the standards? Promise comes with a test script that simply requires us to expose a deferred method (exports.Deferred method) in a CommonJS module, as shown at the end of the code above. Then execute the test by executing the following code:

npm i -g promises-aplus-tests
promises-aplus-tests Promise.jsCopy the code

Other questions about promises

Promise performance issues

You might wonder what performance issues Promise has. It’s not a lot of computation, it’s almost all code that handles logic.

In theory, you can’t call it a “performance problem,” just a possible delay problem. Remember we said we need to package 4 blocks of code in setTimeout. Consider the following code first:

var start = +new Date()
function foo() {
  setTimeout(function() {
    console.log('setTimeout')
    if((+new Date) - start < 1000) {
      foo()
    }
  })
}
foo()Copy the code

How many times does it print ‘setTimeout’ when I run the code above? You can try it yourself, but if nothing goes wrong, it should be about 250 times. I just ran it once and it was 241 times. This shows that the interval between the two setTimeout runs in the above code is about 4ms (in addition, setInterval is also the same). In fact, this is exactly the interval between the two Event loops in the browser. You can check the relevant standards by yourself. Also, in Node, the interval is different from the browser, which in my tests is 1ms.

A single 4ms delay might not be a problem in a typical Web application, but in the extreme case where we have 20 Promise chained calls, plus the running time of the code, the first and last lines of the chained call will probably run over 100ms. If there is no UPDATE to the UI in between, there is no performance problem per se, but there may be some stuttering or flickering, which is not common in Web applications, but can happen in Node applications. So an implementation that can be used in a production environment needs to eliminate this delay. In Node, we can call Process. nextTick or setImmediate (which Q does). How to do this in a browser is beyond the scope of this article. But it needs to call all the queued functions asynchronously and as soon as possible, and here’s an implementation.

How do I stop a Promise chain?

In some cases, we might get a long Promise chained call, and an error in one step makes it unnecessary to run all the code after the chained call, like this (then/catch is omitted here) :

new Promise(function(resolve, reject) {
  resolve(42)
})
  .then(function(value) {
    // "Big ERROR!!!"
  })
  .catch()
  .then()
  .then()
  .catch()
  .then()Copy the code

Suppose this Big ERROR!! There is no need for us to run all the following code, but there is catch and then after the chain call, no matter we are return or throw, we will inevitably enter a catch or THEN, is there any way to make the chain call in Big ERROR!! And then stop, and not execute all the callback functions after the chain call at all?

At first when I encounter this problem is a mystery, and searched on the Internet also does not have the result, some people say that you can judge the Error types in each catch, if not handle it and then throw, there are some other ways, but always want to to make some changes to existing code and all local should follow these conventions, is in trouble.

However, when I look at it from an implementor’s point of view, I do find the answer, which is to return a Promise after a Big ERROR, but the Promise’s executor function does nothing, which means that the Promise will remain pending forever. Since the Promise returned by then will directly take the state of the Promise that is always pending, the returned Promise will always be pending, and the following code will never execute:

new Promise(function(resolve, reject) {
  resolve(42)
})
  .then(function(value) {
    // "Big ERROR!!!"
    return new Promise(function(){})
  })
  .catch()
  .then()
  .then()
  .catch()
  .then()Copy the code

This approach may seem a bit copycat, but it does solve the problem. It introduced a new problem is behind all of the callback function called chain cannot be reclaimed by the garbage collector (in a on implementation, Promise should be deleted after the execution of the all the callback reference of all the callback function so that they can be recycled, in the realization of the above, in order to reduce the complexity, didn’t do this kind of processing). However, if we do not use anonymous functions, but use function definitions or function variables, it is acceptable that only one copy of these functions is in memory in the Promise chain that needs to be executed multiple times.

We can increase the readability of our code by wrapping a Promise that returns nothing into a semantic function:

Promise.cancel = Promise.stop = function() {
  return new Promise(function(){})
}Copy the code

Then we can use it like this:

new Promise(function(resolve, reject) {
  resolve(42)
})
  .then(function(value) {
    // "Big ERROR!!!"
    return Promise.stop()
  })
  .catch()
  .then()
  .then()
  .catch()
  .then()Copy the code

Doesn’t it look a lot more semantic?

What if the last Promise returned on the Promise chain fails?

Consider the following code:

new Promise(function(resolve) {
  resolve(42)
})
  .then(function(value) {
    alter(value)
  })Copy the code

At first glance it looks like there’s nothing wrong, but if you run this code you’ll notice that nothing happens, neither alert 42, nor an error on the console. If you look at the last line, alert is called alter, and the console does not raise an error, because the function where ALTER is located is wrapped in a try/catch block. This error happens to be the rejection reason of the Promise returned by then.

In other words, errors in the last then of the Promise chain are very difficult to find. Some articles have pointed out that it is possible to add a catch at the end of all the Promise chains, so that the error can be caught. This method is indeed feasible, but first add almost the same code everywhere. It violates the DRY principle, and is also rather cumbersome. In addition, the last catch still returns a Promise, and unless you can guarantee that the function in the catch will not fail again, the problem remains. There’s a method in Q called done, and if you chain it to the end of the Promise chain, it catches any errors that haven’t been handled before. It’s not that different from adding a catch to each chain. It’s just that the framework does this, providing a catch chain that doesn’t go wrong. We can implement the done method like this:

Promise.prototype.done = function(){return this.catch(function(e) {console.error(e)})}Copy the code

But is it possible to catch errors at the end of the Promise chain without adding catch or done? Again, the answer is yes.

We can check the onRejectedCallback array for a Promise when it is rejected. If it is empty, the error will not be handled by the function. In this case, we need to print the error to the console for the developer to find. The concrete implementation is as follows:

function reject(reason) {
  setTimeout(function() {
    if (self.status === 'pending') {
      self.status = 'rejected'
      self.data = reason
      if (self.onRejectedCallback.length === 0) {
        console.error(reason)
      }
      for (var i = 0; i < self.rejectedFn.length; i++) {
        self.rejectedFn[i](reason)
      }
    }
  })
}Copy the code

The above code also works fine for the following Promise chains:

new Promise(function(){ // promise1
  reject(3)
})
  .then() // returns promise2
  .then() // returns promise3
  .then() // returns promise4Copy the code

Promise1, 2,3, and 4 have no handlers, so it’s not going to print this error 4 times on the console. In fact, promise1, 2, and 3 implicitly have handlers for then, You’ll remember that the default arguments to THEN are eventually pushed into the Promise callback array. Only promise4 really has no callback, because its then method is not called at all.

In fact, both Bluebird and ES6 Promises do something similar, printing an error to the console when a Promise is rejected but without a callback.

Q uses the done method for a similar purpose, and $Q has added a similar feature in the latest version.

Angular $q interactions with other promises

In general, we don’t use other Promises in Angular because Angular already integrates $q, but sometimes we need libraries in Angular (such as LeanCloud’s JS SDK) that encapsulate ES6 promises. Or you implement promises yourself, and if you use these libraries in Angular, you may find that the view is out of sync with the Model. The reason is that $Q already integrates Angular’s digest loop mechanism, which triggers digest when a Promise is resolved or reject. Other promises are obviously not integrated, so the view will not sync if you run code like this:

app.controller(function($scope) {
  Promise.resolve(42).then(function(value) {
    $scope.value = value
  })
})Copy the code

The digest is not triggered when the Promise ends, so the view is not synchronized. $q happens to have a when method that converts other promises to $q promises (some Promise implementations provide a promise.cast function that converts a thenable into its Promise), and the problem is solved:

app.controller(function($scope, $q) {
  $q.when(Promise.resolve(42)).then(function(value) {
    $scope.value = value
  })
})Copy the code

Of course, there are other solutions, such as adding a digest at the end of other Promise chains, like this:

$digest = function() {$rootScope.$digest() return this} .then(function(value) { $scope.value = value }) .$digest()Copy the code

Since there are not many usage scenarios, we will not discuss them in depth here.

Do I throw new Error() or return promise.reject (new Error())?

Here I think mainly from the performance and coding comfort point of view:

In terms of performance, throw new Error() causes code to enter the logic of a catch block (remember we wrapped all callbacks in a try/catch). It is said that too many throws can affect performance, since code can jump to unexpected locations when thrown.

However, considering that the onResolved/onRejected function is directly packaged in the try in the Promise implementation, the error will directly enter the corresponding catch block of the try, and the jump “range” of the code is relatively small, I think the performance loss can be ignored. You can test it out if you have a chance.

With promise.reject (new Error()), you need to construct a new Promise object with two arrays and four functions: resolve/ Reject, onResolved/onRejected. This also takes time and memory.

From the perspective of coding comfort, throw is used for error, and return is used for normal, which can obviously distinguish error from normal. Throw and return are both keywords, and it is symmetric to deal with the corresponding situation (-_-). Also, in normal editors, promise. reject is not highlighted in the same color as throw and return. Finally, Error highlighting is gone if the developer doesn’t like constructing an Error object.

In summary, I think it’s better to throw an explicit error in a Promise, rather than explicitly construct a reject Promise object.

Best practices

Here are two more best practices

  1. One is don’t write Promise as a nested structure, as for how to improve, here is not much to say

    Then (function(value) {promise1. Then (function(value) {promise1. Then (function(value) {promise1. Then (function(value) {promise1.Copy the code
  2. Second, chained promises return a Promise, not just construct a Promise

    Resolve (1). Then (function(){promise.resolve (2)}). Then (function(){promise.resolve (3)}).Copy the code

Promise related implementation of the Convenience Method

Promise. Race, Promise. All, Promise. Resolve, Promise. All other methods can be implemented conveniently by relying on THEN.

conclusion

Finally, if you find this article helpful, feel free to share it with your friends or team, and remember to cite the source