предисловие
в предыдущем посте«Исходный код обещания: реализация простого обещания»Среди них мы реализуем обещание, которое можно легко использовать. Но на самом деле у него много недостатков, таких как:
- Если разрешение синхронизируется непосредственно в конструкторе промисов, то выполняться не будет.
- Только решить, не отвергать.
- Некоторые крайние случаи не рассматриваются.
Затем изучите принцип реализации 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 обычно не сообщает об ошибке в скрипте, а вызывает отказ функция, требующая внимания.