Front-end talk: Как реализовать обещание?

Promise

Во-первых, что такое обещание?

Промис — это объект, который может когда-то в будущем выдать единственное значение: либо разрешенное значение, либо причина, по которой оно не разрешено (например, произошла ошибка сети).Промис может находиться в одном из 3 возможных состояний: выполнено , отклонено или находится в ожидании. Пользователи Promise могут прикреплять обратные вызовы для обработки выполненного значения или причины отклонения.

Ключевое утверждение: Обещание — это объект, который производит один результат в какой-то момент в будущем. С точки зрения непрофессионала, Promise представляет собой значение, но мы не знаем, когда это значение будет возвращено.

A promise is an object that may produce a single value some time in the future.

Краткий взгляд на историю обещаний

  • Обещания были созданы в 1980-х годах

  • Официальное название в 1988 году: Promise.

  • Многие люди узнали об обещаниях, но люди по-прежнему настаивают на использовании метода, рекомендованного в node.js, для обработки асинхронного кода, передавая объект ошибки в качестве первого параметра функции обратного вызова.

  • DojoПервое крупномасштабное использование Promises, соответствующееPromise/AПредлагается стандартизировать реализацию Promises

  • JQuery начал с промисов и действительно сделал промисы известными

  • JQuery не реализует некоторые функции обещания, которые также привели к образованию стандарта PROMIE / A +

  • ES6 официально представляет Promises и совместим с существующими библиотеками, реализующими спецификацию Promise/A.

Перед реализацией промисов давайте посмотрим, какие характеристики у промисов.

  1. Обещание - этоthenableОбъекты, что есть Обещание.then()метод
  2. Ожиданное обещание может быть выполнено и отклонено
  3. После того, как обещание выполнено или отклонено, больше не меняет свое состояние.
  4. Как только обещание меняет свое состояние, картридж имеет значение (это значение может быть неопределенным).

Начать реализацию обещания

Во-первых, давайте рассмотрим наиболее распространенный фрагмент асинхронного кода:

// 异步方法定义
var basicAsyncFunc = function(callback) {
  setTimeout(function() {
    var randomNumber = Math.random()
    if (randomNumber > 0.5) {
      callback(null, randomNumber)
    } else {
      callback(new Error('bad luck...'))
    }
  }, 1000)
}

// 异步方法调用
basicAsyncFunc((err, result) => {
  if (err) {
    console.log(`the reason fail is: ${err}`)
    return
  }
  console.log(`success get result: ${result}`)
})

Согласно спецификации Promise, идеальным способом вызова Promise является:

// Promise 形式的异步方法定义
var promiseAsyncFunc = function() {}

// Promise 形式的异步方法调用
promiseAsyncFunc.then(
  data => {
    console.log(`success get result: ${data}`)
  },
  err => {
    console.log(`the reason fail is: ${err}`)
  }
)

Согласно этому идеальному способу их вызова, напишем первую версию кода.

Первая версия Promise: может сохранять методы обратного вызова

// Promise 形式的异步方法定义
var promiseAsyncFunc = function() {
  var fulfillCallback
  var rejectCallback

  setTimeout(() => {
    var randomNumber = Math.random()
    if (randomNumber > 0.5) fulfillCallback(randomNumber)
    else rejectCallback(randomNumber)
  }, 1000)
  return {
    then: function(_fulfillCallback, _rejectCallback) {
      fulfillCallback = _fulfillCallback
      rejectCallback = _rejectCallback
    }
  }
}

// Promise 形式的异步方法调用
promiseAsyncFunc().then(fulfillCallback, rejectCallback)

Наше мышление.then()В методе функции обратного вызова результатов заполнения и отклонения сохраняются, а затем вызываются в асинхронном методе.Поскольку это асинхронный вызов, в соответствии с принципом цикла событий,promiseAsyncFunc().then(fulfillCallback, rejectCallback)Входящий обратный вызов должен быть уже назначен в конце асинхронного вызова.

Обещания второго издания: настоящие конструкторы

В нашей текущей реализации Promise код асинхронной логики и код Promise смешаны вместе, давайте различать их:

var promiseAsyncFunc = function() {
  var fulfillCallback
  var rejectCallback

  return {
    fulfill: function(value) {
      if (fulfillCallback && typeof fulfillCallback === 'function') {
        fulfillCallback(value)
      }
    },
    reject: function(err) {
      if (rejectCallback && typeof rejectCallback === 'function') {
        rejectCallback(err)
      }
    },
    then: function(_fulfillCallback, _rejectCallback) {
      fulfillCallback = _fulfillCallback
      rejectCallback = _rejectCallback
    }
  }
}

let ownPromise = function(asyncCall) {
  let promise = promiseAsyncFunc()
  asyncCall(promise.fulfill, promise.reject)
  return promise
}

// Promise 形式的异步方法调用
ownPromise(function(fulfill, reject) {
  setTimeout(() => {
    var randomNumber = Math.random()
    if (randomNumber > 0.5) fulfill(randomNumber)
    else reject(randomNumber)
  }, 1000)
})

Мы определяем новый методownPromise()Используется для создания промисов и вpromiseAsyncFunc()подвергается вfulfillа такжеrejectИнтерфейс удобен для вызова асинхронного кода.

Есть проблема, мы звонимownPromise()Получив экземпляр обещания, мы можем вызвать его напрямую.fulfill(),reject()Эти два метода, хотя теоретически мы должны выставлять только промисыthen()метод. Поэтому мы используем замыкания, чтобы скрыть эти два метода:

var promiseAsyncFunc = function() {
  var fulfillCallback
  var rejectCallback

  return {
    fulfill: function(value) {
      if (fulfillCallback && typeof fulfillCallback === 'function') {
        fulfillCallback(value)
      }
    },
    reject: function(err) {
      if (rejectCallback && typeof rejectCallback === 'function') {
        rejectCallback(err)
      }
    },
    promise: {
      then: function(_fulfillCallback, _rejectCallback) {
        fulfillCallback = _fulfillCallback
        rejectCallback = _rejectCallback
      }
    }
  }
}

let ownPromise = function(asyncCall) {
  let defer = promiseAsyncFunc()
  asyncCall(defer.fulfill, defer.reject)
  return defer.promise
}

// Promise 形式的异步方法调用
ownPromise(function(fulfill, reject) {
  setTimeout(() => {
    var randomNumber = Math.random()
    if (randomNumber > 0.5) fulfill(randomNumber)
    else reject(randomNumber)
  }, 1000)
})

Обещание версии 3: поддержка управления состоянием

Чтобы реализовать требования к изменению состояния промиса в спецификации, нам нужно добавить в промис управление состоянием.Этот шаг относительно прост.Давайте посмотрим на код:

const PENDING = Symbol('pending')
const FULFILLED = Symbol('fulfilled')
const REJECTED = Symbol('rejected')

// Promise 形式的异步方法定义
var promiseAsyncFunc = function() {
  var status = PENDING
  var fulfillCallback
  var rejectCallback

  return {
    fulfill: function(value) {
      if (status !== PENDING) return
      if (typeof fulfillCallback === 'function') {
        fulfillCallback(value)
        status = FULFILLED
      }
    },
    reject(error) {
      if (status !== PENDING) return
      if (typeof rejectCallback === 'function') {
        rejectCallback(error)
        status = REJECTED
      }
    },
    promise: {
      then: function(_fulfillCallback, _rejectCallback) {
        fulfillCallback = _fulfillCallback
        rejectCallback = _rejectCallback
      }
    }
  }
}

let ownPromise = function(asyncCall) {
  let defer = promiseAsyncFunc()
  asyncCall(defer.fulfill, defer.reject)
  return defer.promise
}

// Promise 形式的异步方法调用
ownPromise(function(fulfill, reject) {
  setTimeout(() => {
    var randomNumber = Math.random()
    if (randomNumber > 0.5) fulfill(randomNumber)
    else reject(randomNumber)
  }, 1000)
}).then(data => console.log(data), err => console.log(err))

В этом коде мы используемSymbolДля представления констант состояния учащиеся, не знакомые с символом, могутСмотри сюда

Чтобы судить о статусе обещания, мы присоединилисьfulfillа такжеrejectдва метода. И судить о текущем состоянии обещания в нем. Если он не ожидает выполнения, вернитесь напрямую (поскольку состояние промиса может измениться только один раз).

Теперь наше обещание реализует спецификацию управления состоянием:

  • Допускается только одно изменение состояния
  • Только из ожидающих => выполненных или ожидающих => отклоненных

Но у нашего обещания есть проблема: значение обещания не сохраняется. Если обещание вызывается после завершения асинхронного вызова.then()Метод, мы не можем пройти результат асинхронного вызова функции обратного вызова. Для этого нам нужно добавить поле значение для обещания:

Версия 4 Обещание: сохранить результат асинхронного вызова

Мы добавляем поле значения, чтобы промис сохранить результат выполнения промиса.

// Promise 形式的异步方法定义
var promiseAsyncFunc = function() {
  var status = PENDING
  var fulfillCallback
  var rejectCallback
  var value

  return {
    fulfill: function(_value) {
      if (status !== PENDING) return
      value = _value
      status = FULFILLED
      if (typeof fulfillCallback === 'function') {
        fulfillCallback(value)
      }
    },
    reject(error) {
      if (status !== PENDING) return
      value = error
      status = REJECTED
      if (typeof rejectCallback === 'function') {
        rejectCallback(error)
      }
    },
    promise: {
      then: function(_fulfillCallback, _rejectCallback) {
        fulfillCallback = _fulfillCallback
        rejectCallback = _rejectCallback
      }
    }
  }
}

Здесь мы находим еще одну проблему, если промис ужеfulfillилиrejectусловие. мы звоним сноваthen()метод, входящий метод обратного вызова никогда не будет вызван (поскольку статус больше не ожидает).

Поэтому нам нужноthen()Метод оценивает его статус:

// Promise 形式的异步方法定义
var promiseAsyncFunc = function() {
  var status = PENDING
  var fulfillCallback
  var rejectCallback
  var value

  return {
    fulfill: function(_value) {
      if (status !== PENDING) return
      value = _value
      status = FULFILLED
      if (typeof fulfillCallback === 'function') {
        fulfillCallback(value)
      }
    },
    reject(error) {
      if (status !== PENDING) return
      value = error
      status = REJECTED
      if (typeof rejectCallback === 'function') {
        rejectCallback(error)
      }
    },
    promise: {
      then: function(_fulfillCallback, _rejectCallback) {
        if (status === REJECTED) {
          _rejectCallback(value)
          return
        }
        if (status === FULFILLED) {
          _fulfillCallback(value)
          return
        }
        fulfillCallback = _fulfillCallback
        rejectCallback = _rejectCallback
      }
    }
  }
}

Обещание версии 5: поддержка цепочки

Для поддержки связанных вызовов.then()Возвращаемое значение метода должно бытьthenable(Согласно спецификации Promise/A+,.then()Возвращаемое значение метода должно бытьновыйОБЕЩАТЬ

Для этого добавляем инструментальный методmakeThenable(). Если само переданное значение имеетthen()метод, возвращаемое значение напрямую. В противном случае вернитеthen()объект метода. в этом объектеthen()Метод в соответствии с состоянием, которое мы обещаем, и вызов другого метода обратного вызова для создания нового значения.

function makeThenable(value, status){
  if(value && typeof value.then === 'function'){
    return value
  }
  if(status === FULFILLED){
    return {
      then: function(fulfillCallback, rejectCallback){
        return makeThenable(fulfillCallback(value), FULFILLED)
      }
    }
  }
  if(status === REJECTED) {
    return {
      then: function(fulfillCallback, rejectCallback){
        return makeThenable(rejectCallback(value), FULFILLED)
      }
    }
  }
}

С вышеуказаннымmakeThenable()Метод, мы можем обещатьfulfill(),reject()назад установить значение наthenable:

var promiseAsyncFunc = function() {
  var status = PENDING
  var fulfillCallback
  var rejectCallback
  var value

  return {
    fulfill: function(_value) {
      if (status !== PENDING) return
      value = makeThenable(_value, FULFILLED) // 保证当前promise的value为 thenable
      status = FULFILLED
      if (typeof fulfillCallback === 'function') {
        value.then(fulfillCallback)
      }
    },
    reject(error) {
      if (status !== PENDING) return
      value = makeThenable(error, REJECTED) 、、  // 保证当前value为 thenable
      status = REJECTED
      if (typeof rejectCallback === 'function') {
        value.then(null, rejectCallback)
      }
    },
    promise: {
      then: function(){}
    }
  }
}

Далее посмотримthen()метод. Чтобы вернуть новое обещание, мы сначала должны создать новое обещание. Во-вторых, текущее обещаниеfulfill()илиreject()следует вызвать новое обещаниеfullfill()илиreject()метод. Итак, мы здесьfulfullCallbackа такжеrejectCallbackПри назначении текущему промису оберните его. код показывает, как показано ниже:

    promise: {
      then: function(_fulfillCallback, _rejectCallback) {
        let newPromiseAsyncFunc = promiseAsyncFunc()
        let fulfillFunc = function(value) {
          newPromiseAsyncFunc.fulfill(_fulfillCallback(value))
        }
        let rejectFunc = function(err) {
          newPromiseAsyncFunc.fulfill(_rejectCallback(err))
        }
        if (status === PENDING) {
          fulfillCallback = fulfillFunc
          rejectCallback = rejectFunc
        } else {
          value.then(fulfillFunc, rejectFunc)
        }
        return newPromiseAsyncFunc.promise
      }
    }

Таким образом, у нас есть цепное обещание. Давайте проверим это:

const PENDING = Symbol('pending')
const FULFILLED = Symbol('fulfilled')
const REJECTED = Symbol('rejected')

function makeThenable(value, status) {
  if (value && typeof value.then === 'function') {
    return value
  }
  if (status === FULFILLED) {
    return {
      then: function(fulfillCallback, rejectCallback) {
        return makeThenable(fulfillCallback(value), FULFILLED)
      }
    }
  }
  if (status === REJECTED) {
    return {
      then: function(fulfillCallback, rejectCallback) {
        return makeThenable(rejectCallback(value), FULFILLED)
      }
    }
  }
}

// Promise 形式的异步方法定义
var promiseAsyncFunc = function() {
  var status = PENDING
  var fulfillCallback
  var rejectCallback
  var value

  return {
    fulfill: function(_value) {
      if (status !== PENDING) return
      value = makeThenable(_value, FULFILLED)
      status = FULFILLED
      if (typeof fulfillCallback === 'function') {
        value.then(fulfillCallback)
      }
    },
    reject(error) {
      if (status !== PENDING) return
      value = makeThenable(error, REJECTED)
      status = REJECTED
      if (typeof rejectCallback === 'function') {
        value.then(null, rejectCallback)
      }
    },
    promise: {
      then: function(_fulfillCallback, _rejectCallback) {
        let newPromiseAsyncFunc = promiseAsyncFunc()
        let fulfillFunc = function(value) {
          newPromiseAsyncFunc.fulfill(_fulfillCallback(value))
        }
        let rejectFunc = function(err) {
          newPromiseAsyncFunc.fulfill(_rejectCallback(err))
        }
        if (status === PENDING) {
          fulfillCallback = fulfillFunc
          rejectCallback = rejectFunc
        } else {
          value.then(fulfillFunc, rejectFunc)
        }
        return newPromiseAsyncFunc.promise
      }
    }
  }
}

let ownPromise = function(asyncCall) {
  let defer = promiseAsyncFunc()
  asyncCall(defer.fulfill, defer.reject)
  return defer.promise
}

let testChainedPromise = ownPromise(function(fulfill, reject) {
  setTimeout(() => {
    var randomNumber = Math.random()
    if (randomNumber > 0.5) fulfill(randomNumber)
    else reject(randomNumber)
  }, 1000)
})
  .then(
    data => {
      console.log(data)
      return 'return value in then1 fulfill'
    },
    err => {
      console.log(err)
      return 'return value in then1 reject'
    }
  )
  .then(
    data => {
      console.log(data)
      return 'return value in then2 fulfill'
    },
    err => {
      console.log(err)
      return 'return value in then2 reject'
    }
  )
  .then(
    data => {
      console.log(data)
    },
    err => {
      console.log(err)
    }
  )

/**
console output:

0.9931984611850693
return value in then1 fulfill
return value in then2 fulfill
*/

Обещание версии 6: обработка ошибок

Здесь мы толькоАсинхронный вызова такжевыполнить обратный вызовВозникшая ошибка обрабатывается.

прежде всегоасинхронныйчасть вызова, мы будемtry catchUp, вызовите метод reject при возникновении исключения и передайте исключение в качестве параметра.

let ownPromise = function(asyncCall) {
  let defer = promiseAsyncFunc()
  try {
    asyncCall(defer.fulfill, defer.reject)
  } catch (e) {
    defer.reject(e)
  }
  return defer.promise
}

Послеfulfillвозможные исключения. МыfulfillCallback(value)Возможные исключения перехватываются и передаютсяrejectCallback.

function makeThenable(value, status) {
  if (value && typeof value.then === 'function') {
    return value
  }
  if (status === FULFILLED) {
    return {
      then: function(fulfillCallback, rejectCallback) {
        try {
          let newValue = fulfillCallback(value)
          return makeThenable(newValue, FULFILLED)
        } catch (e) {
          return makeThenable(rejectCallback(e), FULFILLED)
        }
      }
    }
  }
  if (status === REJECTED) {
    return {
      then: function(fulfillCallback, rejectCallback) {
        return makeThenable(rejectCallback(value), FULFILLED)
      }
    }
  }
}

Наконец, давайте протестируем полный код:

const PENDING = Symbol('pending')
const FULFILLED = Symbol('fulfilled')
const REJECTED = Symbol('rejected')

function makeThenable(value, status) {
  if (value && typeof value.then === 'function') {
    return value
  }
  if (status === FULFILLED) {
    return {
      then: function(fulfillCallback, rejectCallback) {
        try {
          let newValue = fulfillCallback(value)
          return makeThenable(newValue, FULFILLED)
        } catch (e) {
          return makeThenable(rejectCallback(e), FULFILLED)
        }
      }
    }
  }
  if (status === REJECTED) {
    return {
      then: function(fulfillCallback, rejectCallback) {
        return makeThenable(rejectCallback(value), FULFILLED)
      }
    }
  }
}

// Promise 形式的异步方法定义
var promiseAsyncFunc = function() {
  var status = PENDING
  var fulfillCallback
  var rejectCallback
  var value

  return {
    fulfill: function(_value) {
      if (status !== PENDING) return
      value = makeThenable(_value, FULFILLED)
      status = FULFILLED
      if (typeof fulfillCallback === 'function') {
        value.then(fulfillCallback)
      }
    },
    reject(error) {
      if (status !== PENDING) return
      value = makeThenable(error, REJECTED)
      if (typeof rejectCallback === 'function') {
        value.then(null, rejectCallback)
      }
      status = REJECTED
    },
    promise: {
      then: function(_fulfillCallback, _rejectCallback) {
        let newPromiseAsyncFunc = promiseAsyncFunc()
        let fulfillFunc = function(value) {
          newPromiseAsyncFunc.fulfill(_fulfillCallback(value))
        }
        let rejectFunc = function(err) {
          newPromiseAsyncFunc.fulfill(_rejectCallback(err))
        }
        if (status === PENDING) {
          fulfillCallback = fulfillFunc
          rejectCallback = rejectFunc
        } else {
          value.then(fulfillFunc, rejectFunc)
        }
        return newPromiseAsyncFunc.promise
      }
    }
  }
}

let ownPromise = function(asyncCall) {
  let defer = promiseAsyncFunc()
  try {
    asyncCall(defer.fulfill, defer.reject)
  } catch (e) {
    defer.reject(e)
  }
  return defer.promise
}

let testChainedPromise = ownPromise(function(fulfill, reject) {
  throw Error('here is an error in asyncCall')
  setTimeout(() => {
    var randomNumber = Math.random()
    if (randomNumber > 0.5) fulfill(randomNumber)
    else reject(randomNumber)
  }, 1000)
})
  .then(
    data => {
      console.log(data)
      return 'return value in then1 fulfill'
    },
    err => {
      console.log(err.message)
      return 'return value in then1 reject'
    }
  )
  .then(
    data => {
      console.log(data)
      throw Error('here is an error in fulfill1')
      return 'return value in then2 fulfill'
    },
    err => {
      console.log(err.message)
      return 'return value in then2 reject'
    }
  )
  .then(
    data => {
      console.log(data)
    },
    err => {
      console.log(err.message)
    }
  )


// console out:
Error: here is an error in asyncCall
return value in then1 reject
Error: here is an error in fulfill1
return value in then2 reject

Суммировать

Вышеупомянутое является нашимPromiseПростая реализация, реализующая основную ссылкуQ-A promise library for javascript. Функция Promise в этой реализации относительно проста, и реализована только часть API/спецификации. Любые комментарии и предложения приветствуются для обмена в области комментариев ;)

Дальнейшее чтение && Цитаты

Для объяснения использования Promise и обработки ошибок:

medium.com/JavaScript-…

Одна из библиотек реализации Promise:QОбъяснение реализации обещания:

GitHub.com/Kris KO WA out/Пожалуйста…

Хотите больше интерфейса и визуализации данных?

вот мойвнешний интерфейс,D3.js,визуализация данныхАдрес гитхаба, добро пожаловать на звездочку и форк :tada:

ssthouse-blog

Если вы считаете, что эта статья хороша, вы можете щелкнуть ссылку ниже, чтобы обратить внимание :)

Главная страница GitHub

Знай колонку

Наггетс

Хотите связаться со мной напрямую?

Электронная почта: ssthouse@163.com

Добро пожаловать, чтобы обратить внимание на мой общедоступный номер: