js — это пошаговое обещание, написанное от руки

JavaScript

Асинхронность во внешнем интерфейсе всегда была обычным явлением, а промисы — это тема, которую нельзя обойти стороной. В интернете есть много пакетных реализаций по этому поводу, но я все равно хочу упаковать его шаг за шагом.Это как играть в апгрейд игры и давать мне напрямую номер полного уровня. Самому апгрейдиться не так весело.

содержание

  • 1 Начните с простого использования и осознайте общую структуру MyPromise
  • 2 Улучшить MyPromise, добавить асинхронную обработку и реализовать экземпляр для многократного вызова метода then.
  • 3 Продолжайте совершенствоваться, реализуйте цепной вызов MyPromise

1 Начните с простого использования и осознайте общую структуру MyPromise

Давайте рассмотрим небольшой пример использования промисов:

let p = new Promise(function (resolve, reject) {
  console.log('start')
  resolve('data1')
})
p.then(
  (v) => {
    console.log('success: ' + v)
  },
  (v) => {
    console.log('error: ' + v)
  }
)
console.log('end')

Результаты приведены ниже:

Для этого примера поясняются следующие моменты, которые также нужно запомнить непосредственно, потому что это как формула решения математической задачи, и ее нужно запомнить в начале.

  1. Promise — это конструктор, а у нового экземпляра есть метод then.
  2. При передаче нового промиса передается параметр, который является функцией, также известной как функция-исполнитель (executor), и исполнитель будет вызван немедленно, поэтому start выводится первым в приведенном выше результате.
  3. executor — это функция, которая принимает два параметра resolve reject , которые также являются функциями.
  4. Экземпляр после нового обещания имеет состояние. Состояние по умолчанию — ожидание. Когда исполнитель вызывает разрешение, состояние экземпляра — это состояние успеха, а когда исполнитель вызывает отклонение, состояние экземпляра — состояние отказа.
  5. Обещание переводится как обещание.Как только состояние экземпляра изменяется, его нельзя изменить снова, он не может выполниться успешно, а затем снова потерпеть неудачу, или наоборот.
  6. У каждого экземпляра промиса есть метод с двумя параметрами,Я привык называть первый параметр обратным вызовом успеха, а второй параметр обратным вызовом отказа., эти два параметра также являются функциями.Когда исполнитель вызывает разрешение, будет выполнена первая функция параметра. Когда исполнитель вызывает reject, будет выполнена вторая функция параметра.

Итак, с текущими функциями или правилами давайте начнем писать конструктор MyPromise.

1 Параметры конструктора будут выполняться сразу во время нового процесса

// 因为会立即执行这个执行器函数
function MyPromise(executor){
  executor(resolve, reject) 
}

2 Новый экземпляр имеет метод then

MyPromise.prototype.then = function(onFulfilled, onRejected){
    
}

3 Новый экземпляр имеет состояние по умолчанию, и исполнитель выполняет команду resolve или reject для изменения состояния.

function MyPromise(executor){
  let self = this
  self.status = 'pending' // 默认promise状态是pending
  function resolve(value){
    self.status = 'resolved' // 成功状态
  }
  function reject(reason){
    self.status = 'rejected' //失败状态
  }
  executor(resolve, reject) 
}

4 Когда исполнитель вызывает разрешение, будет выполнена первая функция параметра (успешный обратный вызов), а когда исполнитель вызывает отклонение, будет выполнена вторая функция параметра (ошибка обратного вызова), затем будет выполнена

MyPromise.prototype.then = function(onFulfilled, onRejected){
  let self = this
  if(self.status === 'resolved'){
    onFulfilled()
  }
  if(self.status === 'rejected'){
    onRejected()
  }
}

5 Убедитесь, что после изменения состояния экземпляра промиса его нельзя изменить снова, а состояние можно изменить только тогда, когда оно находится в ожидании.

function Promise(executor){
  let self = this
  self.status = 'pending' // 默认promise状态是pending
  function resolve(value){
    if(self.status === 'pending'){ //保证状态一旦变更,不能再次修改
      self.value = value
      self.status = 'resolved' // 成功状态
    }
  }
  function reject(reason){
    if(self.status === 'pending'){
      self.reason = reason
      self.status = 'rejected' //失败状态
    }
  }
  executor(resolve, reject)
}

6 Исполнитель выполняет значение, переданное методом разрешения, и передает его первой функции параметров в

function MyPromise(executor){
  let self = this
  self.value = undefined
  self.reason = undefined
  self.status = 'pending' // 默认promise状态是pending
  function resolve(value){
    if(self.status === 'pending'){ //保证状态一旦变更,不能再次修改
      self.value = value
      self.status = 'resolved' // 成功状态
    }
  }
  function reject(reason){
    if(self.status === 'pending'){
      self.reason = reason
      self.status = 'rejected' //失败状态
    }
  }
  executor(resolve, reject) // 因为会立即执行这个执行器函数
}

MyPromise.prototype.then = function(onFulfilled, onRejected){
  let self = this
  if(self.status === 'resolved'){
    onFulfilled(self.value)
  }
  if(self.status === 'rejected'){
    onRejected(self.reason)
  }
}

Попробуйте использовать это MyPromise :

let p = new MyPromise(function (resolve, reject) {
  console.log('start')
  resolve('data2')
})
p.then(
  (v) => {
    console.log('success ' + v)
  },
  (v) => {
  console.log('error ' + v)
  }
)
console.log('end')

Результаты приведены ниже:

Резюме: Результат кажется правильным, но он отличается от исходного промиса, то есть порядка вывода оператора успеха Не волнуйтесь, MyPromise еще не написан.

2 Улучшить MyPromise, добавить асинхронную обработку и реализовать экземпляр для многократного вызова метода then.

Давайте рассмотрим небольшой пример использования нативных промисов.

let p = new Promise(function (resolve, reject) {
  console.log('start')
  setTimeout(function(){
      resolve('data1')
  },2000)
})
p.then(
  (v) => {
    console.log('success: ' + v)
  },
  (v) => {
    console.log('error: ' + v)
  }
)
console.log('end')

Текущий результат выглядит следующим образом

Экземпляр вызывает метод then несколько раз (обратите внимание, что это не цепной вызов).

let p = new Promise(function (resolve, reject) {
  console.log('start')
  setTimeout(function(){
      resolve('data1')
  },2000)
})
p.then(
  (v) => {
    console.log('success: ' + v)
  },
  (v) => {
    console.log('error: ' + v)
  }
)
p.then(
  (v) => {
    console.log('success: ' + v)
  },
  (v) => {
    console.log('error: ' + v)
  }
)
console.log('end')

Текущий результат выглядит следующим образом

Итак, для этой асинхронной ситуации и экземпляра p несколько раз вызывается метод then, как мы должны изменить MyPromise выше?

  1. Для асинхронного случая давайте сначала рассмотрим приведенный выше пример.Когда код выполняется в p.then(), разрешение('data1') в методе исполнителя помещается в очередь асинхронных задач с помощью setTimeout,

  2. Другими словами, в это время состояние экземпляра p по-прежнему является состоянием по умолчанию и не изменилось, тогда мы не знаем, выполнять ли первый параметр (успешный обратный вызов) или второй параметр (ошибочный обратный вызов) в то время в это время. время. .

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

  4. На самом деле, это вариант публикации и подписки. Когда мы выполняем P.THEN () один раз, параметры в затем будут сохранены, то есть обратный вызов успеха и обратный вызов отката сохраняется (подписка). Когда исполняющий исполнитель Метод разрешения или метод отклонения, давайте выполним функцию, которую мы просто сохранены (опубликовать).

Код обновления MyPromise на этом этапе выглядит следующим образом

//省略其余等待,突出增加的点,等下发完整版
function MyPromise(executor){
  ...
  // 用来保存then 方法中,第一个参数
  self.onResolvedCallbacks = []
  // 用来保存then 方法中,第二个参数
  self.onRejectedCallbacks = []
  ...
}
MyPromise.prototype.then = function(onFulfilled, onRejected){
  ...
  if(self.status === 'pending'){
  // 订阅
    self.onResolvedCallbacks.push(function(){
      onFulfilled(self.value)
    })
    self.onRejectedCallbacks.push(function(){
      onRejected(self.reason)
    })
  }
  ...
}

Резюме После этой модификации, когда в нашем методе-исполнителе есть асинхронная функция, выполнение p.then сохранит два соответствующих параметра. Так когда его зовут? Ответ, это должно быть, когда выполняется разрешение в исполнителе или когда выполняется отклонение.

Полный код для этого этапа изменения публикуется далее.

function MyPromise(executor){
  let self = this
  self.value = undefined
  self.reason = undefined
  // 默认promise状态是pending
  self.status = 'pending'
  // 用来保存then 方法中,第一个参数
  self.onResolvedCallbacks = []
  // 用来保存then 方法中,第二个参数
  self.onRejectedCallbacks = []
  function resolve(value){
    if(self.status === 'pending'){ //保证状态一旦变更,不能再次修改
      self.value = value
      self.status = 'resolved' // 成功状态
      self.onResolvedCallbacks.forEach(fn => {
        fn()
      })
    }
  }
  function reject(reason){
    if(self.status === 'pending'){
      self.reason = reason
      self.status = 'rejected' //失败状态
      self.onRejectedCallbacks.forEach(fn => {
        fn()
      })
    }
  }
  executor(resolve, reject) // 因为会立即执行这个执行器函数
}

MyPromise.prototype.then = function(onFulfilled, onRejected){
  let self = this
  if(self.status === 'resolved'){
    onFulfilled(self.value)
  }
  if(self.status === 'rejected'){
    onRejected(self.reason)
  }
  if(self.status === 'pending'){
  // 订阅
    self.onResolvedCallbacks.push(function(){
      onFulfilled(self.value)
    })
    self.onRejectedCallbacks.push(function(){
      onRejected(self.reason)
    })
  }
}

Давайте протестируем эту обновленную версию MyPrimise

let p = new MyPromise(function (resolve, reject) {
  console.log('start')
  setTimeout(function(){
      resolve('data1')
  },2000)
})
p.then(
  (v) => {
    console.log('success: ' + v)
  },
  (v) => {
    console.log('error: ' + v)
  }
)
p.then(
  (v) => {
    console.log('success: ' + v)
  },
  (v) => {
    console.log('error: ' + v)
  }
)
console.log('end')

Текущие результаты следующие, показывая начало и конец печати, а также два успеха, напечатанные вместе через две секунды: data1

Резюме: Вот почему вы можете получить значение self.value, об этом стоит подумать

self.onResolvedCallbacks.push(function(){
  onFulfilled(self.value)
}) 

3 Продолжайте совершенствоваться, реализуйте цепной вызов MyPromise

Теплое напоминание, для цепных вызовов это самый сложный этап в написании обещаний от руки.Прежде чем понять следующие операции, я надеюсь, что вы можете прочитать вышеприведенное содержание еще раз, иначе это может вызвать путаницу~

1 Обещание реальной сцены

Возможны следующие сценарии: при первом чтении имени файла после получения имени файла считывается содержимое файла с этим именем. Очевидно, что это две асинхронные операции, причем вторая асинхронная операция зависит от результата первой асинхронной операции.

// 简要说明 创建一个js文件 与这个文件同级的 name.txt, text.txt 
// 其中name.txt内容是text.txt, 而text.txt的内容是 文本1
// node 运行这个js文件

let fs = require('fs')

fs.readFile('./name.txt', 'utf8', function (err, data) {
  console.log(data)
  fs.readFile(data, 'utf8', function (err, data) {
    console.log(data)
  })
})

Текущий результат выглядит следующим образом

Очевидно, что приведенный выше режим обратного вызова — это не то, что нам нужно, так как же сделать так, чтобы описанный выше метод обещал? Чтобы было понятнее, напишу пошагово:

1 Инкапсулируйте функцию, которая возвращает экземпляр обещания

function readFile(url){
  return new Promise((resolve, reject)=>{
  })
}

2 Выполнение этой функции вернет экземпляр обещания, то есть существует метод then, который можно использовать

readFile('./name.txt').then(
  () => {},
  () => {}
)

3 Улучшите функцию-исполнитель, и помните, что функция-исполнитель работает синхронно, то есть при появлении новой исполняющая функция выполняется

let fs = require('fs')

function readFile(url){
  return new Promise((resolve, reject)=>{
    fs.readFile(url, 'utf8', function (err, data) {
      if(err) reject(err)
        resolve(data)
      })
    })
}

readFile('./name.txt').then(
  (data) => { console.log(data) },
  (err) => { console.log(err) }
)

Запустите этот небольшой фрагмент кода, и результат будет следующим

4 Не используйте цепные вызовы

readFile('./name.txt').then(
  (data) => {
    console.log(data)
    readFile(data).then(
      (data) => {console.log(data)},
      (err) => {console.log(err)}
    )
  },
  (err) => {console.log(err)}
)

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

5 Использование связанных вызовов

readFile('./name.txt')
.then(
  (data) => {
    console.log(data)
    return readFile(data)
  },
  (err) => {console.log(err)}
)
.then(
  (data) => { console.log(data) },
  (err) => { console.log(err) }
)

Текущий результат выглядит следующим образом

Вышеизложенное является обещанием простого асинхронного сценария.

2 Просмотрите распространенные сценарии связанных вызовов

На самом деле, про цепные вызовы, у нас тоже есть что-то похожее на правила формул, которые нужно помнить, это спецификация, из промиса А+, портал здесьобещание SA plus.com/,

Я не буду приводить перевод обещания A+ здесь, но я выберу несколько сухих товаров, которые также являются вещами, которые мы привыкли использовать с обещаниями.

  1. Цепной вызов jQuery связан с тем, что jQuery возвращает это, и обещание может продолжаться, потому что метод then обещания возвращает обещание
  2. Возвращается новое обещание, потому что, как упоминалось выше, после изменения состояния экземпляра обещания его нельзя изменить снова, поэтому должно быть возвращено новое обещание.
  3. Два параметра в методе then, то есть так называемый обратный вызов успеха и обратный вызов отказа, как поступить с их возвращаемыми значениями?
  4. Возьмем в качестве примера callback-функцию успеха (первый параметр в then), эта функция возвращает нормальное значение, то есть константу или объект, и это значение будет передано следующему then как успешному результату. Если эта функция возвращает что-то отличное от обычного значения, то возможны два случая.
  5. Необычное значение --- промис: он будет решать, вызывать ли первый параметр или второй параметр следующего затем в зависимости от успеха или неудачи возвращенного промиса.
  6. Необычное значение — например, отчет об ошибке: он будет работать с параметром сбоя в следующем then, который является вторым параметром в then.

Давайте сначала проверим эти случаи с помощью нативных промисов, а затем добавим эти реализации в MyPromise.

Убедитесь, что первый обратный вызов возвращает нормальное значение, и измените приведенный выше пример.

readFile('./name.txt')
.then(
  (data) => {
    console.log(data)
    return {'a': 100} // 1 返回引用类型
    // return 100 // 2 返回基本类型
    // return undefined 3 返回undefined
    // 4 不写return
  },
  (err) => {console.log(err)}
)
.then(
  (data) => { console.log(data) },
  (err) => { console.log(err) }
)

Вышеупомянутые четыре случая соответствуют результатам операции следующим образом:

Убедитесь, что в первую очередь обещается обещание, и как то, как во-вторых, тогда цепочка отвечает?

readFile('./name.txt')
.then(
  (data) => {
    console.log(data)
    return new Promise(function(resolve, reject){
      setTimeout(function(){
      // resolve('ok')
      reject('error')
      },1000)
    })
  },
  (err) => {console.log(err)}
)
.then(
  (data) => { console.log(data) },
  (err) => { console.log(err) }
)

Текущие результаты следующие, которые являются результатами вышеуказанного выполнения resolve и reject соответственно.

Убедитесь, что в первом then возвращается состояние ошибки, как соответствует второй then цепочки

readFile('./name.txt')
.then(
  (data) => {
    console.log(data)
    throw TypeError()
  },
  (err) => {console.log(err)}
)
.then(
  (data) => { console.log(data) },
  (err) => { console.log(err) }
)

Результат выполнения следующий

3 На основе вышеупомянутого идеального цепного вызова MyPromise

1 затем возвращает новое обещание
MyPromise.prototype.then = function(onFulfilled, onRejected){
  let self = this
  return new MyPromise(function(resolve, reject){
	if(self.status === 'resolved'){...}
	if(self.status === 'rejected'){...}
	if(self.status === 'pending'){...}
  }
} 

Резюме: Вы можете обратиться и сравнить реализацию предыдущей версии MyPromise.prototype.then.По сути, это просто исходная логика, обернутая функцией-исполнителем MyPromise, и мы знаем, что функция-исполнитель выполняется синхронно, в новом экземпляре В данный момент будет запущен исполнитель, поэтому в настоящее время добавление этого пакета не влияет на исходную логику и понимает, что пока выполняется метод then, возвращаемый экземпляр обещания является совершенно новым экземпляром обещания. .

2 Обработка возвращаемого значения функции в случае обычного значения
MyPromise.prototype.then = function(onFulfilled, onRejected){
  ...
  if(self.status === 'resolved'){
    try{
      let x = onFulfilled(self.value)
      resolve(x)
    }catch(e){
      reject(e)
    }
  }
  ...
}

Резюме В приведенном выше коде я записал только состояние self.status === 'resolved', и два других состояния записываются таким же образом. Я возьму это в качестве примера для иллюстрации. onFulfilled, который является нашим экземпляром промиса, выполняет первый параметр, переданный методом then.Если он возвращает нормальное значение после выполнения, он напрямую передает это значение функции обратного вызова успеха следующего затем вызываемого в цепочке. (Это выражение должно быть понятно каждому.)

Хорошо, давайте подумаем об этом. На первом шаге был реализован метод then для возврата нового промиса. Затем, если это новое промис должно быть выполнено then, параметры обратного вызова успеха и обратного вызова отказа then являются параметрами Откуда берутся значение, требуемое первым параметром, и причина, требуемая вторым параметром?

Это должно быть в этом новом экземпляре промиса, в новом процессе, в этой функции-обработчике, разрешить или отклонить. Здесь на самом деле есть небольшой поворот.

Чтобы лучше понять вышеизложенное, позвольте мне сделать еще один снимок и просмотреть предыдущий пример.

Каков результат? Всем известно, что он сначала выведет успех Ace, а затем выведет успех undefined.

Следовательно, на приведенном выше рисунке первый затем возвращает новое обещание, но не выполняет resolve и reject, что эквивалентно resolve(undefined) , поэтому второй затем печатает успех undefined

Итак, в этом разделе пусть x = onFulfilled(self.value) Причина здесь, я довольно многословен ~ Конечно, это только случай работы с обычными значениями. Полный код для этого этапа прилагается.

MyPromise.prototype.then = function(onFulfilled, onRejected){
  let self = this
  let promise2 = new MyPromise(function(resolve, reject){
  // then 函数的成功回调函数的执行结果 与 promise2的关系
  if(self.status === 'resolved'){
    try{
      let x = onFulfilled(self.value)
        resolve(x) // 这是 x 是常量的时候,但x可能是一个新的promise,
    }catch(e){
       reject(e)
    }
  }
  if(self.status === 'rejected'){
    try{
      let x = onRejected(self.reason)
        resolve(x)
      }catch(e){
        reject(e)
      }
  }
  if(self.status === 'pending'){
    self.onResolvedCallbacks.push(function(){
      try{
        let x = onFulfilled(self.value)
        resolve(x)
      }catch(e){
        reject(e)
      }
    })
    self.onRejectedCallbacks.push(function(){
      try{
        let x = onRejected(self.reason)
        resolve(x)
      }catch(e){
        reject(e)
      }
    })
 }
})
return promise2
}

Протестируйте приведенный выше пример кода следующим образом.

let p = new MyPromise(function (resolve, reject) {
  console.log('start')
  setTimeout(function(){
    resolve('data1')
  },500)
})
p.then(
  (v) => {
  console.log('success: ' + v)
  // return v // 1 返回 v
  // return 100 // 2 返回常量
  // return {a : 100} // 3 返回对象
  // return undefined // 4 返回 undefined
  // 5 不写return
  },
  (v) => {
  console.log('error: ' + v)
  }
)
.then(
  (v) => {
    console.log('success: ' + v)
  },
  (v) => {
   console.log('error: ' + v)
  }
)
console.log('end')

Результаты, соответствующие пунктам 1-5 выше, следующие.

3 Обработка возвращаемого значения функции в случае необычного значения

То есть для приведенного выше примера происходит шестой случай, то есть первая функция обратного вызова затем возвращает новый экземпляр обещания.

p.then(
  (v) => {
  console.log('success: ' + v)
    return new MyPromise(excutor)
  },
  (v) => {
  console.log('error: ' + v)
  }
)

Первая функция обратного вызова тогда, соответствующая MyPromise, onFulfilled, поэтому мы должны снова преобразовать MyPromise.prototype.then

MyPromise.prototype.then = function(onFulfilled, onRejected){
  let self = this
  let promise2 = new MyPromise(function(resolve, reject){
    // then 函数的成功回调函数的执行结果 与 promise2的关系
    if(self.status === 'resolved'){
      try{
        let x = onFulfilled(self.value)
        // x可能是一个新的promise , 抽离一个函数来处理x的情况
        resolvePromise(promise2, x, resolve, reject)
      }catch(e){
       reject(e)
      }
    }
    if(self.status === 'rejected'){...}
    if(self.status === 'pending'){...}
  })
  return promise2
}