Deep into promise (3) — name promise

Time:2021-3-6

We often encounter this situation: for example, through the user name to find and return the user information and his followers. There are usually two ways:
1. Define an external variable:

var user
getUserByName('nswbmw')
  .then((_user) => {
    user = _user
    return getFollowersByUserId(user._id)
  })
  .then((followers) => {
    return {
      user,
      followers
    }
  })

2. Use closure:

getUserByName('nswbmw')
  .then((user) => {
    return getFollowersByUserId(user._id).then((followers) => {
      return {
        user,
        followers
      }
    })
  })

Both implementations are OK, but neither is very beautiful. So I had an idea before: the parameters of then in the same layer are the reverse order of all then results. This is reflected in the code

Promise.resolve()
  .then(function () {
    return getUserByName('nswbmw')
  })
  .then(function (user) {
    return getFollowersByUserId(user._id)
  })
  .then((followers, user) => {
    return {
      user,
      followers
    }
  })

The third then parameter is the reverse order of the first two then results, namely, followers and user. More complex, such as nested promise I will not list, the key point of this implementation is: how to distinguish the levels of then. From the implementation of appointment, we know that each then returns a new promise, which makes it impossible to know how deep the current then is from the previous nested promise. So this idea can’t be realized.

Name promise

Later, I came up with a better solution than the above, that is, to name promise: the first parameter of the current then is still the return value of the previous promise (that is, compatible with promise / A + specification), and the later parameters use dependency injection. This is reflected in the code

Promise.resolve()
  .then(function user() {
    return getUserByName('nswbmw')
  })
  .then(function followers(_, user) {
    return getFollowersByUserId(user._id)
  })
  .then((_, user, followers) => {
    return {
      user,
      followers
    }
  })

In the above, the callback function of then is named (such as: user). The return value of the callback function is mounted on the promise internal variable (such as: values: {user: ‘xxx’}), and the values of the parent promise are passed to the child promise. The second parameter after then is injected through dependency injection, which is the basic idea of naming promise. We can name the parameters of promise constructor, then callback function and catch callback function.

So, I modified and released the named app package based on the app package.

Named appoint principle: add name and values attributes to a promise, name is the identity of the promise (take the name of the promise constructor parameter, then callback function or catch callback function), and values is an object that stores the name and value of all ancestor promises. When the parent promise state changes, set the value and values of the parent promise( this.values [ this.name ]Then copy the values to the values of the child promise and pass them down in turn. Let’s take another example

const assert = require('assert')
const Promise = require('named-appoint')
new Promise(function username(resolve, reject) {
  setTimeout(() => {
    resolve('nswbmw')
  })
})
.then(function user(_, username) {
  assert(_ === 'nswbmw')
  assert(username === 'nswbmw')
  return {
    name: 'nswbmw',
    age: '17'
  }
})
.then(function followers(_, username, user) {
  assert.deepEqual(_, { name: 'nswbmw', age: '17' })
  assert(username === 'nswbmw')
  assert.deepEqual(user, { name: 'nswbmw', age: '17' })
  return [
    {
      name: 'zhangsan',
      age: '17'
    },
    {
      name: 'lisi',
      age: '18'
    }
  ]
})
.then((_, user, followers, username) => {
  assert.deepEqual(_, [ { name: 'zhangsan', age: '17' }, { name: 'lisi', age: '18' } ])
  assert(username === 'nswbmw')
  assert.deepEqual(user, { name: 'nswbmw', age: '17' })
  assert.deepEqual(followers, [ { name: 'zhangsan', age: '17' }, { name: 'lisi', age: '18' } ])
})
.catch(console.error)

Obviously, there is a prerequisite for naming promise: it is on the same promise chain. The code is as follows:

const assert = require('assert')
const Promise = require('named-appoint')
new Promise(function username(resolve, reject) {
  setTimeout(() => {
    resolve('nswbmw')
  })
})
.then(() => {
  return Promise.resolve()
    .then(function user(_, username) {
      assert(username === undefined)
      return {
        name: 'nswbmw',
        age: '17'
      }
    })
})
.then(function (_, username, user) {
  assert.deepEqual(_, { name: 'nswbmw', age: '17' })
  assert(username === 'nswbmw')
  assert(user === undefined)
})
.catch(console.error)

The last then print undefined, because a new promise chain branch is generated inside.

Combined with CO

There is no change when combined with Co

const Promise = require('named-appoint')
const co = require('co')

const promise = Promise.resolve()
  .then(function user() {
    return 'nswbmw'
  })
  .then(function followers() {
    return [{ name: 'zhangsan' }, { name: 'lisi' }]
  })
  .then((_, user, followers) => {
    return {
      user,
      followers
    }
  })
co(function *() {
  console.log(yield promise)
  /*
  { user: 'nswbmw',
    followers: [ { name: 'zhangsan' }, { name: 'lisi' } ] }
  */
}).catch(console.error)

By the way, I made a promise / A + + specification without authorization.

“Critical” error handling

Let’s continue with the brain hole. The error handling in swift is as follows:

do {
  try getFollowers("nswbmw")
} catch AccountError.No_User {
  print("No user")
} catch AccountError.No_followers {
  print("No followers")
} catch {
  print("Other error")
}

You can set catch to catch only specific exception errors. If the previous catch did not catch errors, then the error will be caught by the last catch. JavaScript can also achieve similar functions by naming the catch callback function. I modified and published the condition appoint package on the basis of appoint. Take an example

var Promise = require('condition-appoint')
Promise.reject(new TypeError('type error'))
  .catch(function SyntaxError(e) {
    console.error('SyntaxError: ', e)
  })
  .catch(function TypeError(e) {
    console.error('TypeError: ', e)
  })
  .catch(function (e) {
    console.error('default: ', e)
  })

It will be captured by the second catch, which is printing:

TypeError: [TypeError: type error]
Revise:

var Promise = require('condition-appoint')
Promise.reject(new TypeError('type error'))
  .catch(function SyntaxError(e) {
    console.error('SyntaxError: ', e)
  })
  .catch(function ReferenceError(e) {
    console.error('ReferenceError: ', e)
  })
  .catch(function (e) {
    console.error('default: ', e)
  }) 

It will be captured by the third catch, which is printing:

default:  [TypeError: type error]

Because there is no corresponding error catch function, it is finally caught by an anonymous catch. Let’s revise it again

var Promise = require('condition-appoint')
Promise.reject(new TypeError('type error'))
  .catch(function SyntaxError(e) {
    console.error('SyntaxError: ', e)
  })
  .catch(function (e) {
    console.error('default: ', e)
  })
  .catch(function TypeError(e) {
    console.error('TypeError: ', e)
  }) 

It will be captured by the second catch, which is printing:

default:  [TypeError: type error]

Because it was caught in advance by the anonymous catch method.

The implementation principle of condition appoint is very simple. Three lines of code are added in then of appoint

Promise.prototype.then = function (onFulfilled, onRejected) {
  ...
  if (isFunction(onRejected) && this.state === REJECTED) {
    if (onRejected.name && ((this.value && this.value.name) !== onRejected.name)) {
      return this;
    }
  }
  ...
};

Judge whether the name of the incoming callback function and the error name are equal. If they are not anonymous functions and are not equal, skip the catch statement by return this, that is, real value.

Of course, condition appoint is also valid for custom errors, as long as the name attribute is set for the custom error.

Recommended Today

Third party calls wechat payment interface

Step one: preparation 1. Wechat payment interface can only be called if the developer qualification has been authenticated on wechat open platform, so the first thing is to authenticate. It’s very simple, but wechat will charge 300 yuan for audit 2. Set payment directory Login wechat payment merchant platform( pay.weixin.qq . com) — > Product […]