предисловие
Многие новички в JavaScript чувствовали страх перед адом обратных вызовов, и только когда они освоили синтаксис Promise, они почувствовали облегчение. Хотя многие языки уже имеют встроенные промисы, именно jQuery 1.5 действительно продвигает их в JavaScript.$.ajax
Рефакторинг поддерживает Promise, а использование также совпадает с цепочкой вызовов, которую рекомендует jQuery. Позже родился ES6, и все начали вступать в эпоху универсальных промисов, а позже в ES8 появился асинхронный синтаксис, который сделал асинхронное написание JavaScript более элегантным.
Сегодня мы будем шаг за шагом реализовывать промис.Если вы не использовали промисы, то перед чтением этой статьи рекомендуется ознакомиться с синтаксисом промисов.
Конструктор
в существующихPromise/A+
Технические характеристикине указывает, откуда берется объект обещания, в jQuery, вызывая$.Deferred()
Получить объект обещания В ES6 объект обещания получается путем создания экземпляра класса Promise. Здесь мы используем синтаксис ES для создания класса и возвращаем объект промиса путем создания экземпляра.Поскольку промис уже существует, мы временно назовем этот классDeferred
.
class Deferred {
constructor(callback) {
const resolve = () => {
// TODO
}
const reject = () => {
// TODO
}
try {
callback(resolve, reject)
} catch (error) {
reject(error)
}
}
}
Конструктор принимает обратный вызов.При вызове обратного вызова вам нужно передать два метода: разрешить и отклонить.
Состояние обещаний
Обещания делятся на три состояния:
- ⏳
pending
: Ожидание, это начальное состояние Promise; - 🙆♂️
fulfilled
: все закончилось, состояние вызова разрешается нормально; - 🙅♂️
rejected
: Отклонено, есть внутренняя ошибка или состояние после вызова reject;
Мы видим, что промисы имеют состояние во время выполнения, хранящееся в[[PromiseState]]
середина. Далее мы добавляем состояние в Deferred.
//基础变量的定义
const STATUS = {
PENDING: 'PENDING',
FULFILLED: 'FULFILLED',
REJECTED: 'REJECTED'
}
class Deferred {
constructor(callback) {
this.status = STATUS.PENDING
const resolve = () => {
// TODO
}
const reject = () => {
// TODO
}
try {
callback(resolve, reject)
} catch (error) {
// 出现异常直接进行 reject
reject(error)
}
}
}
Здесь есть еще одна интересная вещь: состояние выполнения в ранней реализации браузера разрешено, что явно не соответствует спецификации Promise. Конечно, сейчас это исправлено.
Внутренние результаты
Помимо состояния внутри промиса есть результат[[PromiseResult]]
, используемый для временного хранения значений, принятых resolve/reject .
Идем дальше и добавляем внутренний результат в конструктор.
class Deferred {
constructor(callback) {
this.value = undefined
this.status = STATUS.PENDING
const resolve = value => {
this.value = value
// TODO
}
const reject = reason => {
this.value = reason
// TODO
}
try {
callback(resolve, reject)
} catch (error) {
// 出现异常直接进行 reject
reject(error)
}
}
}
сохранить обратный вызов
При использовании промисов мы обычно вызываем объект промиса.then
метод, когда состояние обещания превращается вfulfilled
илиrejected
, получить внутренний результат, а затем выполнить последующую обработку. Итак, в конструкторе вам также нужно создать два массива для хранения.then
Обратный вызов, переданный методом.
class Deferred {
constructor(callback) {
this.value = undefined
this.status = STATUS.PENDING
this.rejectQueue = []
this.resolveQueue = []
const resolve = value => {
this.value = value
// TODO
}
const reject = reason => {
this.value = reason
// TODO
}
try {
callback(resolve, reject)
} catch (error) {
// 出现异常直接进行 reject
reject(error)
}
}
}
resolve
а такжеreject
Изменить статус
Далее нам нужно реализовать методы resolve и reject, которые при вызове изменят состояние объекта обещания. И после вызова любого метода другой метод не может быть вызван.
new Promise((resolve, reject) => {
setTimeout(() => {
resolve('🙆♂️')
}, 500)
setTimeout(() => {
reject('🙅♂️')
}, 800)
}).then(
() => {
console.log('fulfilled')
},
() => {
console.log('rejected')
}
)
В этот момент консоль просто выведетfulfilled
, не появляетсяrejected
.
class Deferred {
constructor(callback) {
this.value = undefined
this.status = STATUS.PENDING
this.rejectQueue = []
this.resolveQueue = []
let called // 用于判断状态是否被修改
const resolve = value => {
if (called) return
called = true
this.value = value
// 修改状态
this.status = STATUS.FULFILLED
}
const reject = reason => {
if (called) return
called = true
this.value = reason
// 修改状态
this.status = STATUS.REJECTED
}
try {
callback(resolve, reject)
} catch (error) {
// 出现异常直接进行 reject
reject(error)
}
}
}
Обратный звонок
После изменения состояния обещание, которое получает результат, обычно вызывает обратный вызов, переданный методом then.
class Deferred {
constructor(callback) {
this.value = undefined
this.status = STATUS.PENDING
this.rejectQueue = []
this.resolveQueue = []
let called // 用于判断状态是否被修改
const resolve = value => {
if (called) return
called = true
this.value = value
// 修改状态
this.status = STATUS.FULFILLED
// 调用回调
for (const fn of this.resolveQueue) {
fn(this.value)
}
}
const reject = reason => {
if (called) return
called = true
this.value = reason
// 修改状态
this.status = STATUS.REJECTED
// 调用回调
for (const fn of this.rejectQueue) {
fn(this.value)
}
}
try {
callback(resolve, reject)
} catch (error) {
// 出现异常直接进行 reject
reject(error)
}
}
}
Студенты, знакомые с системой событий JavaScript, должны знать,promise.then
Обратный вызов в методе будет помещен в очередь микрозадач, а затем вызван асинхронно.
Поэтому нам нужно поставить вызов обратного вызова в асинхронную очередь, где мы можем поставить его в setTimeout, чтобы сделать отложенный вызов, хотя он не соответствует спецификации, но это будет сделано в ближайшее время.
class Deferred {
constructor(callback) {
this.value = undefined
this.status = STATUS.PENDING
this.rejectQueue = []
this.resolveQueue = []
let called // 用于判断状态是否被修改
const resolve = value => {
if (called) return
called = true
// 异步调用
setTimeout(() => {
this.value = value
// 修改状态
this.status = STATUS.FULFILLED
// 调用回调
for (const fn of this.resolveQueue) {
fn(this.value)
}
})
}
const reject = reason => {
if (called) return
called = true
// 异步调用
setTimeout(() =>{
this.value = reason
// 修改状态
this.status = STATUS.REJECTED
// 调用回调
for (const fn of this.rejectQueue) {
fn(this.value)
}
})
}
try {
callback(resolve, reject)
} catch (error) {
// 出现异常直接进行 reject
reject(error)
}
}
}
затем метод
Затем нам нужно реализовать метод then.Учащиеся, которые использовали Promise, должны знать, что метод then может продолжать вызываться в цепочке, поэтому then должен возвращать объект обещания. Но когдаPromise/A+
В спецификации четко оговорено, что метод then возвращает новый объект промиса, а не возвращает это напрямую, что мы можем проверить с помощью следующего кода.
можно увидетьp1
объект иp2
Это два разных объекта, а то, что затем способ возвращаетсяp2
Объекты также являются экземплярами промисов.
Кроме того, метод then также должен оценивать текущее состояние, если текущее состояние неpending
состояние, вы можете напрямую вызвать входящий обратный вызов, не помещая его в очередь для ожидания.
class Deferred {
then(onResolve, onReject) {
if (this.status === STATUS.PENDING) {
// 将回调放入队列中
const rejectQueue = this.rejectQueue
const resolveQueue = this.resolveQueue
return new Deferred((resolve, reject) => {
// 暂存到成功回调等待调用
resolveQueue.push(function (innerValue) {
try {
const value = onResolve(innerValue)
// 改变当前 promise 的状态
resolve(value)
} catch (error) {
reject(error)
}
})
// 暂存到失败回调等待调用
rejectQueue.push(function (innerValue) {
try {
const value = onReject(innerValue)
// 改变当前 promise 的状态
resolve(value)
} catch (error) {
reject(error)
}
})
})
} else {
const innerValue = this.value
const isFulfilled = this.status === STATUS.FULFILLED
return new Deferred((resolve, reject) => {
try {
const value = isFulfilled
? onResolve(innerValue) // 成功状态调用 onResolve
: onReject(innerValue) // 失败状态调用 onReject
resolve(value) // 返回结果给后面的 then
} catch (error) {
reject(error)
}
})
}
}
}
Теперь, когда наша логика в основном работает, давайте попробуем запустить фрагмент кода:
new Deferred(resolve => {
setTimeout(() => {
resolve(1)
}, 3000)
}).then(val1 => {
console.log('val1', val1)
return val1 * 2
}).then(val2 => {
console.log('val2', val2)
return val2
})
Через 3 секунды консоль показывает следующие результаты:
Как видите, это в основном соответствует нашим ожиданиям.
проникновение стоимости
Если мы вызовем then без каких-либо параметров, согласно спецификации значение текущего промиса может быть прозрачно передано следующему методу then. Например, следующий код:
new Deferred(resolve => {
resolve(1)
})
.then()
.then()
.then(val => {
console.log(val)
})
Я не вижу никакого вывода в консоли, но переключаюсь на Promise, чтобы увидеть правильный результат.
Решить это очень просто, просто нужно судить, является ли параметр функцией, когда вызывается then, если нет, нужно указать значение по умолчанию.
const isFunction = fn => typeof fn === 'function'
class Deferred {
then(onResolve, onReject) {
// 解决值穿透
onReject = isFunction(onReject) ? onReject : reason => { throw reason }
onResolve = isFunction(onResolve) ? onResolve : value => { return value }
if (this.status === STATUS.PENDING) {
// ...
} else {
// ...
}
}
}
Теперь мы можем получить правильный результат.
шаг далеко
Теперь мы всего в одном шаге от идеальной реализации метода then, что мы и передаем при вызове метода then.onResolve/onReject
При обратном вызове вам также необходимо оценить их возвращаемое значение. Что нам делать, если внутри обратного вызова возвращается объект обещания? Или если есть циклическая ссылка, что нам делать?
Мы получаемonResolve/onReject
После возвращаемого значения он вызывается напрямуюresolve
илиresolve
, и теперь нам нужно немного обработать их возвращаемые значения.
then(onResolve, onReject) {
// 解决值穿透代码已经省略
if (this.status === STATUS.PENDING) {
// 将回调放入队列中
const rejectQueue = this.rejectQueue
const resolveQueue = this.resolveQueue
const promise = new Deferred((resolve, reject) => {
// 暂存到成功回调等待调用
resolveQueue.push(function (innerValue) {
try {
const value = onResolve(innerValue)
- resolve(value)
+ doThenFunc(promise, value, resolve, reject)
} catch (error) {
reject(error)
}
})
// 暂存到失败回调等待调用
rejectQueue.push(function (innerValue) {
try {
const value = onReject(innerValue)
- resolve(value)
+ doThenFunc(promise, value, resolve, reject)
} catch (error) {
reject(error)
}
})
})
return promise
} else {
const innerValue = this.value
const isFulfilled = this.status === STATUS.FULFILLED
const promise = new Deferred((resolve, reject) => {
try {
const value = isFulfilled
? onResolve(innerValue) // 成功状态调用 onResolve
: onReject(innerValue) // 失败状态调用 onReject
- resolve(value)
+ doThenFunc(promise, value, resolve, reject)
} catch (error) {
reject(error)
}
})
return promise
}
}
возвращаемое оценочное суждение
Когда мы используем промис, мы часто возвращаем новый промис в методе then, а затем передаем внутренний результат нового промиса в последующий метод then.
fetch('server/login')
.then(user => {
// 返回新的 promise 对象
return fetch(`server/order/${user.id}`)
})
.then(order => {
console.log(order)
})
function doThenFunc(promise, value, resolve, reject) {
// 如果 value 是 promise 对象
if (value instanceof Deferred) {
// 调用 then 方法,等待结果
value.then(
function (val) {
doThenFunc(promise, val, resolve, reject)
},
function (reason) {
reject(reason)
}
)
return
}
// 如果非 promise 对象,则直接返回
resolve(value)
}
Проверка циклических ссылок
Если возвращаемое значение функции обратного вызова текущего метода then представляет собой новый объект обещания, сгенерированный текущим методом then, он считается циклической ссылкой. Конкретные случаи следующие:
новый объект обещания, возвращаемый методом thenp1
, которое используется в качестве возвращаемого значения в обратном вызове, и будет выдано исключение. Потому что согласно предыдущей логике, код всегда будет застревать в этом куске логики.
Поэтому нам нужно заранее предупреждать и вовремя выбрасывать ошибки.
function doThenFunc(promise, value, resolve, reject) {
// 循环引用
if (promise === value) {
reject(
new TypeError('Chaining cycle detected for promise')
)
return
}
// 如果 value 是 promise 对象
if (value instanceof Deferred) {
// 调用 then 方法,等待结果
value.then(
function (val) {
doThenFunc(promise, val, resolve, reject)
},
function (reason) {
reject(reason)
}
)
return
}
// 如果非 promise 对象,则直接返回
resolve(value)
}
Теперь давайте попробуем вернуть новый объект обещания в then .
const delayDouble = (num, time) => new Deferred((resolve) => {
console.log(new Date())
setTimeout(() => {
resolve(2 * num)
}, time)
})
new Deferred(resolve => {
setTimeout(() => {
resolve(1)
}, 2000)
})
.then(val => {
console.log(new Date(), val)
return delayDouble(val, 2000)
})
.then(val => {
console.log(new Date(), val)
})
Приведенные выше результаты также полностью соответствуют нашим ожиданиям.
Уловить метод
Метод catch на самом деле очень прост и эквивалентен сокращению метода then.
class Deferred {
constructor(callback) {}
then(onResolve, onReject) {}
catch(onReject) {
return this.then(null, onReject)
}
}
статический метод
resolve/reject
Класс Promise также предоставляет два статических метода, которые напрямую возвращают объект обещания, состояние которого было исправлено.
class Deferred {
constructor(callback) {}
then(onResolve, onReject) {}
catch(onReject) {}
static resolve(value) {
return new Deferred((resolve, reject) => {
resolve(value)
})
}
static reject(reason) {
return new Deferred((resolve, reject) => {
reject(reason)
})
}
}
all
Метод all принимает массив объектов обещаний и ожидает, пока состояние всех объектов обещаний в массиве не станетfulfilled
, а затем вернуть результат, который также является массивом, и каждое значение массива соответствует внутреннему результату объекта обещания.
Во-первых, нам нужно определить, является ли входящий параметр массивом, а затем создать массив результатов и новый объект обещания.
class Deferred {
static all(promises) {
// 非数组参数,抛出异常
if (!Array.isArray(promises)) {
return Deferred.reject(new TypeError('args must be an array'))
}
// 用于存储每个 promise 对象的结果
const result = []
const length = promises.length
// 如果 remaining 归零,表示所有 promise 对象已经 fulfilled
let remaining = length
const promise = new Deferred(function (resolve, reject) {
// TODO
})
return promise
}
}
Затем нам нужно принять решение о перехвате разрешения каждого объекта обещания, и каждое разрешение должно бытьremaining
минус один покаremaining
сбросить на ноль.
class Deferred {
static all(promises) {
// 非数组参数,抛出异常
if (!Array.isArray(promises)) {
return Deferred.reject(new TypeError('args must be an array'))
}
const result = [] // 用于存储每个 promise 对象的结果
const length = promises.length
let remaining = length
const promise = new Deferred(function (resolve, reject) {
// 如果数组为空,则返回空结果
if (promises.length === 0) return resolve(result)
function done(index, value) {
doThenFunc(
promise,
value,
(val) => {
// resolve 的结果放入 result 中
result[index] = val
if (--remaining === 0) {
// 如果所有的 promise 都已经返回结果
// 然后运行后面的逻辑
resolve(result)
}
},
reject
)
}
// 放入异步队列
setTimeout(() => {
for (let i = 0; i < length; i++) {
done(i, promises[i])
}
})
})
return promise
}
}
Ниже мы используем следующий код, чтобы определить, правильна ли логика. Как и ожидалось, после выполнения кода через 3 секунды консоль выводит массив[2, 4, 6]
.
const delayDouble = (num, time) => new Deferred((resolve) => {
setTimeout(() => {
resolve(2 * num)
}, time)
})
console.log(new Date())
Deferred.all([
delayDouble(1, 1000),
delayDouble(2, 2000),
delayDouble(3, 3000)
]).then((results) => {
console.log(new Date(), results)
})
Приведенные выше результаты в основном соответствуют нашим ожиданиям.
race
Метод гонки также принимает массив объектов обещаний, но ему нужно только одно обещание, которое становитсяfulfilled
Статус вернет результат.
class Deferred {
static race(promises) {
if (!Array.isArray(promises)) {
return Deferred.reject(new TypeError('args must be an array'))
}
const length = promises.length
const promise = new Deferred(function (resolve, reject) {
if (promises.length === 0) return resolve([])
function done(value) {
doThenFunc(promise, value, resolve, reject)
}
// 放入异步队列
setTimeout(() => {
for (let i = 0; i < length; i++) {
done(promises[i])
}
})
})
return promise
}
}
Давайте изменим предыдущий случай проверки метода all на race. Как и ожидалось, после запуска кода через 1 секунду консоль выводит 2.
const delayDouble = (num, time) => new Deferred((resolve) => {
setTimeout(() => {
resolve(2 * num)
}, time)
})
console.log(new Date())
Deferred.race([
delayDouble(1, 1000),
delayDouble(2, 2000),
delayDouble(3, 3000)
]).then((results) => {
console.log(new Date(), results)
})
Приведенные выше результаты в основном соответствуют нашим ожиданиям.
Суммировать
Упрощенная версия класса Promise уже реализована, некоторые детали здесь опущены, и доступен полный кодgithub. Появление Promise заложило прочный фундамент для более позднего асинхронного синтаксиса.В следующем блоге можно рассказать об истории асинхронного программирования в JavaScript, а для себя случайно вырыть яму. . .