Исходный код обещания: выполнять разрешение синхронно

внешний интерфейс исходный код JavaScript Promise

предисловие

в предыдущем посте«Исходный код обещания: реализация простого обещания»Среди них мы реализуем обещание, которое можно легко использовать. Но на самом деле у него много недостатков, таких как:

  1. Если разрешение синхронизируется непосредственно в конструкторе промисов, то выполняться не будет.
  2. Только решить, не отвергать.
  3. Некоторые крайние случаи не рассматриваются.

Затем изучите принцип реализации Promise, прочитав исходный код проекта then/promise на github.

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

new Promise(function (resolve) {
  resolve(1);
}).then(function (val) {
  console.log(val);
});

Примечание. На этот раз я прочитал версию 3.0.0 then/promise, пожалуйста, отметьте исходный код.здесь.

интерпретировать

Реализация кода Promise в проекте then/promise находится только в файле index.js, а это менее 100 строк кода, поэтому его не так сложно прочитать.

nextTick

Сначала разогреемся, давайте посмотрим на этот конец кода:

var nextTick

if (typeof setImmediate === 'function') { // IE >= 10 & node.js >= 0.10
  nextTick = function(fn){ setImmediate(fn) }
} else if (typeof process !== 'undefined' && process && typeof process.nextTick === 'function') { // node.js before 0.10
  nextTick = function(fn){ process.nextTick(fn) }
} else {
  nextTick = function(fn){ setTimeout(fn, 0) }
}

Этот фрагмент кода связан с циклом событий js.Когда стек контекста выполнения пуст, будет выполняться указанная выше очередь задач. Решение здесь в основном совместимо с узлом и браузерами, и приведенный выше код можно рассматривать как setTimeout.

Promise

После разминки следующее, на что нужно обратить внимание, — это реализация Promise. Сначала код пишется в конструкторе, все реализации кода находятся в его конструкторе, а затем открывается функция then.

function Promise(fn) {
    // ...
    
    then.then = function() {
        // ...
    }
    
    // ...
}

В конструкторе определено множество переменных и функций, которые пока не будут объясняться по отдельности, а будут объясняться при их использовании.

Давайте сначала посмотрим на два шага выполнения синхронного и асинхронного:

// 同步
new Promise(function (resolve) {
  resolve(1);
}).then(function (val) {
  console.log(val);
});

// 异步
new Promise(function (resolve) {
  setTimeout(function () {
    resolve(1);
  }, 1000);
}.then(function (val) {
  console.log(val);
});

При синхронном и асинхронном порядке выполнения функций разный.

constructor -> fn --同步--> resolve(reject) -> then -> then 回调
constructor -> fn --异步--> then -> resolve(reject) -> then 回调

Синхронизировать

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

new Promise(function (resolve) {
  resolve(1);
}).then(function (val) {
  console.log(val);
});

Начнем с выполнения fn.Если fn не выполнится, он поймает и вызовет reject:

try { fn(resolve, reject) }
catch(e) { reject(e) }

resolve

Затем начните выполнение функции разрешения, передав 1 в качестве параметра. Давайте взглянем на реализацию resolve, сначала удалим некоторый код, который пока не нужен:

function resolve(newValue) {
  resolve_(newValue)
}

function resolve_(newValue) {
  if (state !== null)
    return
  try {
    state = true
    value = newValue
    finale()
  } catch (e) { reject_(e) }
}

Здесь используются две переменные, состояние и значение, а также вызывается финальная функция.

state — это переменная, используемая для запоминания состояния, выполнения resolve для успешного присвоения true, выполнения reject для успешного присвоения false, в противном случае она равна нулю. Переменная используется только дляпредотвратить множественные разрешения, пока вызывающий код вызывает разрешение, последующее разрешение будет недействительным.

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

Финал временно игнорируется, потому что код в финале почти не выполняется при его синхронном выполнении.

then

После выполнения функции разрешения выполните функцию then:

this.then = function(onFulfilled, onRejected) {
  return new Promise(function(resolve, reject) {
    handle({ onFulfilled: onFulfilled, onRejected: onRejected, resolve: resolve, reject: reject })
  })
}

На данный момент игнорируйте возвращенный экземпляр Promise, это для последующих, а затем цепных вызовов. Здесь после выполнения функции then функции обратного вызова then onFulfilled и onRejected передаются в качестве параметров в функцию дескриптора. onFulfilled — это обратный вызов после успешного решения, а onRejected — обратный вызов после успешного отклонения.

handle

Перейти к функции дескриптора:

function handle(deferred) {
  nextTick(function() {
    var cb = state ? deferred.onFulfilled : deferred.onRejected
    if (typeof cb !== 'function'){
      (state ? deferred.resolve : deferred.reject)(value)
      return
    }
    var ret
    try {
      ret = cb(value)
    }
    catch (e) {
      deferred.reject(e)
      return
    }
    deferred.resolve(ret)
  })
}

Основная функция дескриптора — выполнение кода cb. Другие коды выносят суждения, чтобы судить, является ли сходство cb функцией. Таким образом, функция дескриптора в это время выполняет функцию обратного вызова then, передавая значение, сохраненное до разрешения, в качестве параметра.

NextTick здесь практически бесполезен, потому что код выполняется синхронно, сработает при асинхронности. последний казненныйdeferred.resolve(ret)Это также необходимо для реализации цепочек вызовов, и в настоящее время нет большой разницы между выполнением и выполнением.

На этом синхронизация Promise завершена, давайте еще раз рассмотрим последовательность выполнения:

constructor -> fn --同步--> resolve(reject) -> then -> then 回调

Суммировать

Код, который выполняется синхронно на протяжении веков, легче понять, ведь он выполняется последовательно. Посмотрите еще раз на синхронный код выполнения Promise:

new Promise(function (resolve) {
  resolve(1);
}).then(function (val) {
  console.log(val);
});

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

Порядок выполнения следующий:

constructor -> fn --同步--> resolve(reject) -> then -> then 回调

В то же время в коде реализации Promise используется много try catch, после сообщения catch будет выполнено reject вместо resolve, так что Promise обычно не сообщает об ошибке в скрипте, а вызывает отказ функция, требующая внимания.