Promiseに深く入り込む(三)——命名Promise


私たちはよくこのような状況に遭遇します.例えばユーザ名で検索して、そのユーザ情報と彼の注目者に戻ります.通常は2つの方法があります.
  • は外部変数を定義します.
    var usergetUserByName('nswbmw')
      .then((_user) => {
        user = _user
        return getFollowersByUserId(user._id)
      })
      .then((followers) => {
        return {
          user,
          followers
        }
      })
  • は、クローズドパケットを使用する:
    getUserByName('nswbmw')
      .then((user) => {
        return getFollowersByUserId(user._id).then((followers) => {
          return {
            user,
            followers
          }
        })
      })
  • 二つの実現は全部できますが、あまり綺麗ではありません.そこで以前に同じ層のthenのパラメータが以前のすべてのthen結果の逆順であるという考えが生まれた.コードに反映されるのは:
    Promise.resolve()
      .then(function () {
        return getUserByName('nswbmw')
      })
      .then(function (user) {
        return getFollowersByUserId(user._id)
      })
      .then((followers, user) => {
        return {
          user,
          followers
        }
      })
    3番目のthenのパラメータは、最初の2つのthen結果の逆順、すなわちフォロワーとuserである.もっと複雑です.例えば、プロミセを入れ子にした場合は、このような実現のポイントは、thenの階層をどうやって区別するかです.apointの実現から、各thenは新たなpromiseを返すことを知っています.これは現在のthenが前から入れ子の深いpromiseを知ることができなくなりました.だからこの考えは実現できない.
    名前はPromise
    後に、上記より良い解決方法、すなわち名前Promise:現在のthenの最初のパラメータは依然として前のpromiseの戻り値(すなわちPromise/A+仕様に対応)であり、後のパラメータは依存注入を使用している.コードに反映されるのは:
    Promise.resolve()
      .then(function user() {
        return getUserByName('nswbmw')
      })
      .then(function followers(_, user) {
        return getFollowersByUserId(user._id)
      })
      .then((_, user, followers) => {
        return {
          user,
          followers
        }
      })
    上にはthenへのコールバック関数の名前が付けられています.このコールバック関数の戻り値はプロミス内部変数にマウントされています.thenの2番目の後のパラメータは注入に依存して注入を実現し,これがPromiseと命名して実現する基本的な考え方である.Promiseコンストラクタのパラメータ、thenコールバック関数、catchコールコールバック関数に名前を付けることができます.
    そこで、apointパッケージをもとに修正し、named-apointパッケージを発表しました.
    named-apoint原理:promiseにnameとvalues属性を追加しました.nameはこのpromiseの標識です.(Promise構造関数のパラメータ、thenコールバック関数またはcatchコールコールコールコールコールバック関数の名前).valuesはすべての祖先promiseのnameとvalueを対象として記憶しています.父亲のプロミスの状态が変わったら、父亲のプロミスのvalueとvaluesをセットして、次のように伝えます.もう一つの例を見ます
    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)
    明らかにPromiseという名前の前提条件は同じpromiseチェーンにあります.次のコード:
    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)
    最後のthenはundefinedをプリントします.内部に新しいpromiseチェーンの分岐が発生しました.
    結合co使用
    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)
    ちなみに勝手にPromise/A++仕様を作りました.
    『好き嫌い』エラー処理
    私たちは引き続き脳の穴を開けます.Swiftでのエラー処理はこうです.
    do {
      try getFollowers("nswbmw")
    } catch AccountError.No_User {
      print("No user")
    } catch AccountError.No_followers {
      print("No followers")
    } catch {
      print("Other error")
    }
    catchは特定の異常なエラーだけをキャプチャするように設定できます.これまでのcatchに誤りがなければ、エラーは最後のcatchに捕獲されます.catchフィードバック関数という名前でJavaScriptも同様の機能を実現できます.appintに基づいて修正してconditionn-appintパッケージを発表しました.例を見ます
    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)
      })
    二つ目のcatchに捕獲されます.すなわち印刷します.
    TypeError:  [TypeError: type error]
    修正:
    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)
      })
    3番目のcatchに捕獲されます.すなわち、印刷します.
    default:  [TypeError: type error]
    対応するエラーcatch関数がないので、最終的に匿名のcatchに捕獲されます.もう一度修正します
    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)
      })
    二つ目のcatchに捕獲されます.すなわち印刷します.
    default:  [TypeError: type error]
    事前に匿名のcatch方法で捕獲されたからです.
    conditionn-apoint原理は簡単で、apointのthenリガに3行のコードがあります.
    Promise.prototype.then = function (onFulfilled, onRejected) {
      ...
      if (isFunction(onRejected) && this.state === REJECTED) {
        if (onRejected.name && ((this.value && this.value.name) !== onRejected.name)) {
          return this;
        }
      }
      ...};
    入力されたコールバック関数名とエラー名が等しいかどうかを判断します.匿名関数ではなく、同じではない場合は、リターンthisによってこのcatch文をスキップします.すなわち、実際の値が透過します.
    もちろん、conditionn-appintはカスタムエラーにも有効です.カスタムエラーでname属性が設定されています.