Автор новыйasync/await
Меня привлекла его функция приостановки выполнения, я подумал, что без поддержки нативного API await может приостановить текущий метод и реализовать приостановку выполнения, мне было очень любопытно. Любопытство подтолкнуло меня к тому, чтобы слой за слоем снять все об асинхронном программировании в JS. Прочитав эту статью, читатель должен понять:
-
Promise
Принцип реализации -
async/await
Принцип реализации -
Generator
Принцип реализации
Обещание реализации
В процессе написания автор прочитал много статей, объясняющих реализацию Promise, но я чувствую, что большинство статей трудно назвать связными. порекомендуйте студентам, у которых есть голова, тянуть непосредственно к резюме этой главы, чтобы увидеть окончательную реализацию.В сочетании с комментариями вы можете понять код напрямую.
Возвращаясь к теме, в начале статьи давайте сначала нажмем на то, какую проблему решает для нас Promise: в традиционном асинхронном программировании, если есть зависимость между асинхронностью, нам нужно удовлетворить эту зависимость путем вложения callback-ов слой за слоем. слишком много слоев, удобочитаемость и ремонтопригодность становятся очень плохими, что приводит к так называемому «аду обратных вызовов», в то время как Promise изменяет вложенность обратных вызовов в цепные вызовы для повышения удобочитаемости и удобства обслуживания. Давайте реализуем обещание шаг за шагом:
1. Шаблон наблюдателя
Давайте сначала рассмотрим простейшее использование Promise:
const p1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('result')
},
1000);
})
p1.then(res => console.log(res), err => console.log(err))
Глядя на этот пример, мы анализируем процесс вызова Promise:
-
Promise
Конструктор получаетexecutor()
,существуетnew Promise()
Обратный вызов исполнителя выполняется немедленно, когда -
executor()
Внутренние асинхронные задачи помещаются в очередь макро/микрозадач, ожидающих выполнения. -
then()
Выполнено, собрать обратные вызовы успеха/неудачи, поместить в очередь успеха/неудачи -
executor()
Асинхронная задача выполняется, вызываяresolve/reject
, принимать обратные вызовы из очереди успеха/неудачи и выполнять их последовательно
Фактически, студенты, знакомые с шаблонами проектирования, могут легко понять, что этоШаблон наблюдателя, это收集依赖 -> 触发通知 -> 取出依赖执行
Метод широко используется при реализации паттерна Observer, в Promise порядок выполнения следующий:then收集依赖 -> 异步触发resolve -> resolve执行依赖
. Исходя из этого, мы можем наметить примерную форму промиса:
class MyPromise {
// 构造方法接收一个回调
constructor(executor) {
this._resolveQueue = [] // then收集的执行成功的回调队列
this._rejectQueue = [] // then收集的执行失败的回调队列
// 由于resolve/reject是在executor内部被调用, 因此需要使用箭头函数固定this指向, 否则找不到this._resolveQueue
let _resolve = (val) => {
// 从成功队列里取出回调依次执行
while(this._resolveQueue.length) {
const callback = this._resolveQueue.shift()
callback(val)
}
}
// 实现同resolve
let _reject = (val) => {
while(this._rejectQueue.length) {
const callback = this._rejectQueue.shift()
callback(val)
}
}
// new Promise()时立即执行executor,并传入resolve和reject
executor(_resolve, _reject)
}
// then方法,接收一个成功的回调和一个失败的回调,并push进对应队列
then(resolveFn, rejectFn) {
this._resolveQueue.push(resolveFn)
this._rejectQueue.push(rejectFn)
}
}
После написания кода мы можем протестировать его:
const p1 = new MyPromise((resolve, reject) => {
setTimeout(() => {
resolve('result')
}, 1000);
})
p1.then(res => console.log(res))
//一秒后输出result
Мы используем шаблон наблюдателя, чтобы просто реализовать его.then
иresolve
, чтобы мы могли получить возвращаемое значение асинхронной операции в обратном вызове метода then, но до финальной реализации нашему промису еще далеко, добавим этот промис шаг за шагом:
2. Спецификация Promise A+
Мы просто реализовали сверхнизкопрофильную версию Promise выше, но мы увидим, что многие статьи отличаются от того, что мы написали, и их реализация Promise также вводит различные элементы управления состоянием.Это потому, что реализация Promise ES6 должна следоватьСпецификация Обещание/A+, это спецификация, которая требует контроля состояния Promise. Спецификация Promise/A+ относительно длинная, здесь всего два основных правила:
- Обещание — это, по сути, конечный автомат, и состояния могут быть только следующими тремя:
Pending(等待态)
,Fulfilled(执行态)
,Rejected(拒绝态)
, изменение состояния одностороннее, только из Pending -> Fulfilled или Pending -> Rejected, изменение состояния необратимоthen方法
Получает два необязательных параметра, соответствующих обратному вызову, запускаемому при изменении состояния. Метод then возвращает обещание. Метод then может вызываться несколько раз одним и тем же промисом.
Согласно спецификации добавим код Promise:
//Promise/A+规范的三种状态
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'
class MyPromise {
// 构造方法接收一个回调
constructor(executor) {
this._status = PENDING // Promise状态
this._resolveQueue = [] // 成功队列, resolve时触发
this._rejectQueue = [] // 失败队列, reject时触发
// 由于resolve/reject是在executor内部被调用, 因此需要使用箭头函数固定this指向, 否则找不到this._resolveQueue
let _resolve = (val) => {
if(this._status !== PENDING) return // 对应规范中的"状态只能由pending到fulfilled或rejected"
this._status = FULFILLED // 变更状态
// 这里之所以使用一个队列来储存回调,是为了实现规范要求的 "then 方法可以被同一个 promise 调用多次"
// 如果使用一个变量而非队列来储存回调,那么即使多次p1.then()也只会执行一次回调
while(this._resolveQueue.length) {
const callback = this._resolveQueue.shift()
callback(val)
}
}
// 实现同resolve
let _reject = (val) => {
if(this._status !== PENDING) return // 对应规范中的"状态只能由pending到fulfilled或rejected"
this._status = REJECTED // 变更状态
while(this._rejectQueue.length) {
const callback = this._rejectQueue.shift()
callback(val)
}
}
// new Promise()时立即执行executor,并传入resolve和reject
executor(_resolve, _reject)
}
// then方法,接收一个成功的回调和一个失败的回调
then(resolveFn, rejectFn) {
this._resolveQueue.push(resolveFn)
this._rejectQueue.push(rejectFn)
}
}
3. Цепной вызов потом
После завершения спецификации давайте реализуем цепные вызовы.Это ключевой и сложный момент реализации Promise.Давайте сначала посмотрим, как затем происходит цепочка:
const p1 = new Promise((resolve, reject) => {
resolve(1)
})
p1
.then(res => {
console.log(res)
//then回调中可以return一个Promise
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(2)
}, 1000);
})
})
.then(res => {
console.log(res)
//then回调中也可以return一个值
return 3
})
.then(res => {
console.log(res)
})
вывод
1
2
3
Давайте подумаем, как реализовать этот связанный вызов:
- очевидно
.then()
Промис должен быть возвращен, чтобы можно было найти метод then, поэтому мы переносим возвращаемое значение метода then в промис. -
.then()
Обратный вызов должен получить предыдущий.then()
Возвращаемое значение -
.then()
Обратный вызов должен выполняться последовательно.Принимая приведенный выше код в качестве примера, хотя промис возвращается в середине, последовательность выполнения должна быть гарантированно 1-> 2-> 3. Мы должны дождаться изменения текущего состояния Promise, прежде чем выполнять обратный вызов, собранный следующим then, что требует от нас классификации и обсуждения возвращаемого значения then.
// then方法
then(resolveFn, rejectFn) {
//return一个新的promise
return new MyPromise((resolve, reject) => {
//把resolveFn重新包装一下,再push进resolve执行队列,这是为了能够获取回调的返回值进行分类讨论
const fulfilledFn = value => {
try {
//执行第一个(当前的)Promise的成功回调,并获取返回值
let x = resolveFn(value)
//分类讨论返回值,如果是Promise,那么等待Promise状态变更,否则直接resolve
//这里resolve之后,就能被下一个.then()的回调获取到返回值,从而实现链式调用
x instanceof MyPromise ? x.then(resolve, reject) : resolve(x)
} catch (error) {
reject(error)
}
}
//把后续then收集的依赖都push进当前Promise的成功回调队列中(_rejectQueue), 这是为了保证顺序调用
this._resolveQueue.push(fulfilledFn)
//reject同理
const rejectedFn = error => {
try {
let x = rejectFn(error)
x instanceof MyPromise ? x.then(resolve, reject) : resolve(x)
} catch (error) {
reject(error)
}
}
this._rejectQueue.push(rejectedFn)
})
}
Затем мы можем протестировать цепочку вызовов:
const p1 = new MyPromise((resolve, reject) => {
setTimeout(() => {
resolve(1)
}, 500);
})
p1
.then(res => {
console.log(res)
return 2
})
.then(res => {
console.log(res)
return 3
})
.then(res => {
console.log(res)
})
//输出 1 2 3
4. Проникновение ценности и изменение статуса
Мы уже завершили первоначальный вызов цепочки, но для метода then() нам еще нужно разобраться с двумя деталями.
- проникновение стоимости: Согласно спецификации, если параметр, полученный функцией then(), не является функцией, то его следует игнорировать. Если не игнорировать, будет выдано исключение, когда обратный вызов then() не является функцией, что приведет к разрыву цепного вызова.
-
Обрабатывать случаи, когда статус разрешен/отклонен: На самом деле то, как мы написали then() выше, состоит в том, что соответствующее состояние
padding
, но иногда разрешение/отклонение выполняется до then() (например,Promise.resolve().then()
), если обратный вызов then() также помещается в очередь выполнения разрешения/отклонения в это время, обратный вызов не будет выполняться, поэтому статус сталfulfilled
илиrejected
В этом случае мы напрямую выполняем обратный вызов then:
// then方法,接收一个成功的回调和一个失败的回调
then(resolveFn, rejectFn) {
// 根据规范,如果then的参数不是function,则我们需要忽略它, 让链式调用继续往下执行
typeof resolveFn !== 'function' ? resolveFn = value => value : null
typeof rejectFn !== 'function' ? rejectFn = reason => {
throw new Error(reason instanceof Error? reason.message:reason);
} : null
// return一个新的promise
return new MyPromise((resolve, reject) => {
// 把resolveFn重新包装一下,再push进resolve执行队列,这是为了能够获取回调的返回值进行分类讨论
const fulfilledFn = value => {
try {
// 执行第一个(当前的)Promise的成功回调,并获取返回值
let x = resolveFn(value)
// 分类讨论返回值,如果是Promise,那么等待Promise状态变更,否则直接resolve
x instanceof MyPromise ? x.then(resolve, reject) : resolve(x)
} catch (error) {
reject(error)
}
}
// reject同理
const rejectedFn = error => {
try {
let x = rejectFn(error)
x instanceof MyPromise ? x.then(resolve, reject) : resolve(x)
} catch (error) {
reject(error)
}
}
switch (this._status) {
// 当状态为pending时,把then回调push进resolve/reject执行队列,等待执行
case PENDING:
this._resolveQueue.push(fulfilledFn)
this._rejectQueue.push(rejectedFn)
break;
// 当状态已经变为resolve/reject时,直接执行then回调
case FULFILLED:
fulfilledFn(this._value) // this._value是上一个then回调return的值(见完整版代码)
break;
case REJECTED:
rejectedFn(this._value)
break;
}
})
}
5. Совместимость с задачами синхронизации
После завершения связанного вызова then мы имеем дело с предыдущей деталью, а затем выпускаем полный код. Как мы сказали выше, порядок выполнения Promises таков:new Promise -> then()收集回调 -> resolve/reject执行回调
, эта последовательность основана наexecutor — это асинхронная задачаПо предположению, если исполнитель является синхронной задачей, то порядок становитсяnew Promise -> resolve/reject执行回调 -> then()收集回调
, выполнение resolve идет до этого. Чтобы быть совместимым с этой ситуацией, мы даемresolve/reject
Действие, которое выполняет обратный вызов, оборачивает setTimeout, чтобы оно выполнялось асинхронно.
Вставьте здесь предложение, на самом деле есть некоторые сведения об этом setTimeout. Хотя спецификация не требует, чтобы обратные вызовы помещались в очередь макрозадач или в очередь микрозадач, на самом деле реализация Promise по умолчанию помещается в очередь микрозадач.Наша реализация (включая большинство ручных реализаций Promise и преобразование полифилла) использует setTimeout Помещает в очередь задач макросов (конечно, мы также можем использовать MutationObserver для имитации микрозадач)
//Promise/A+规定的三种状态
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'
class MyPromise {
// 构造方法接收一个回调
constructor(executor) {
this._status = PENDING // Promise状态
this._value = undefined // 储存then回调return的值
this._resolveQueue = [] // 成功队列, resolve时触发
this._rejectQueue = [] // 失败队列, reject时触发
// 由于resolve/reject是在executor内部被调用, 因此需要使用箭头函数固定this指向, 否则找不到this._resolveQueue
let _resolve = (val) => {
//把resolve执行回调的操作封装成一个函数,放进setTimeout里,以兼容executor是同步代码的情况
const run = () => {
if(this._status !== PENDING) return // 对应规范中的"状态只能由pending到fulfilled或rejected"
this._status = FULFILLED // 变更状态
this._value = val // 储存当前value
// 这里之所以使用一个队列来储存回调,是为了实现规范要求的 "then 方法可以被同一个 promise 调用多次"
// 如果使用一个变量而非队列来储存回调,那么即使多次p1.then()也只会执行一次回调
while(this._resolveQueue.length) {
const callback = this._resolveQueue.shift()
callback(val)
}
}
setTimeout(run)
}
// 实现同resolve
let _reject = (val) => {
const run = () => {
if(this._status !== PENDING) return // 对应规范中的"状态只能由pending到fulfilled或rejected"
this._status = REJECTED // 变更状态
this._value = val // 储存当前value
while(this._rejectQueue.length) {
const callback = this._rejectQueue.shift()
callback(val)
}
}
setTimeout(run)
}
// new Promise()时立即执行executor,并传入resolve和reject
executor(_resolve, _reject)
}
// then方法,接收一个成功的回调和一个失败的回调
then(resolveFn, rejectFn) {
// 根据规范,如果then的参数不是function,则我们需要忽略它, 让链式调用继续往下执行
typeof resolveFn !== 'function' ? resolveFn = value => value : null
typeof rejectFn !== 'function' ? rejectFn = reason => {
throw new Error(reason instanceof Error? reason.message:reason);
} : null
// return一个新的promise
return new MyPromise((resolve, reject) => {
// 把resolveFn重新包装一下,再push进resolve执行队列,这是为了能够获取回调的返回值进行分类讨论
const fulfilledFn = value => {
try {
// 执行第一个(当前的)Promise的成功回调,并获取返回值
let x = resolveFn(value)
// 分类讨论返回值,如果是Promise,那么等待Promise状态变更,否则直接resolve
x instanceof MyPromise ? x.then(resolve, reject) : resolve(x)
} catch (error) {
reject(error)
}
}
// reject同理
const rejectedFn = error => {
try {
let x = rejectFn(error)
x instanceof MyPromise ? x.then(resolve, reject) : resolve(x)
} catch (error) {
reject(error)
}
}
switch (this._status) {
// 当状态为pending时,把then回调push进resolve/reject执行队列,等待执行
case PENDING:
this._resolveQueue.push(fulfilledFn)
this._rejectQueue.push(rejectedFn)
break;
// 当状态已经变为resolve/reject时,直接执行then回调
case FULFILLED:
fulfilledFn(this._value) // this._value是上一个then回调return的值(见完整版代码)
break;
case REJECTED:
rejectedFn(this._value)
break;
}
})
}
}
Затем мы можем проверить это обещание:
const p1 = new MyPromise((resolve, reject) => {
resolve(1) //同步executor测试
})
p1
.then(res => {
console.log(res)
return 2 //链式调用测试
})
.then() //值穿透测试
.then(res => {
console.log(res)
return new MyPromise((resolve, reject) => {
resolve(3) //返回Promise测试
})
})
.then(res => {
console.log(res)
throw new Error('reject测试') //reject测试
})
.then(() => {}, err => {
console.log(err)
})
// 输出
// 1
// 2
// 3
// Error: reject测试
На данный момент мы реализовали основную функцию Promise(`∀´)Ψ
Остальные несколько методов очень просты, подчистим:
Promise.prototype.catch()
catch()方法
Возвращает обещание и обрабатывает отказ. Он ведет себя так же, как вызов Promise.prototype.then(undefined, onRejected).
//catch方法其实就是执行一下then的第二个回调
catch(rejectFn) {
return this.then(undefined, rejectFn)
}
Promise.prototype.finally()
finally()方法
Возвращает обещание. В конце промиса, независимо от того, выполнен результат или отклонен, будет выполнена указанная функция обратного вызова. После, наконец, вы можете продолжить с then. и передаст значение следующему, а затем без изменений
//finally方法
finally(callback) {
return this.then(
value => MyPromise.resolve(callback()).then(() => value), // MyPromise.resolve执行回调,并在then中return结果传递给后面的Promise
reason => MyPromise.resolve(callback()).then(() => { throw reason }) // reject同理
)
}
P.S. Некоторые студенты спрашивали меняMyPromise.resolve(callback())
Значение , вот дополнительное объяснение: это письмо на самом деле включает в себяfinally()
детали использования,finally () Если возвращается промис в состоянии отклонения, он изменит состояние текущего промиса.,этоMyPromise.resolve
Он используется для изменения состояния промиса.В случае, если finally() не возвращает промис состояния отклонения или выдает ошибку, удалите его.MyPromise.resolve
То же самое (вы можете задавать мне вопросы, процесс исправления ошибок также может углубить ваше понимание Promise, вы можете напрямую @ меня в каждой группе общения)
Использованная литература:Поверхностное понимание Promise.prototype.finally()
Promise.resolve()
Promise.resolve(value)
Метод возвращает объект Promise, который разрешается в заданное значение. Если значение является промисом, верните это промис; если значение является затемабельным (то есть с методом «тогда»), возвращенное промис будет «следовать» за доступным объектом, принимая его конечное состояние; в противном случае возвращенное промис будет end in Это значение завершено. Эта функция сглаживает несколько уровней вложенности объектов, подобных обещаниям.
//静态的resolve方法
static resolve(value) {
if(value instanceof MyPromise) return value // 根据规范, 如果参数是Promise实例, 直接return这个实例
return new MyPromise(resolve => resolve(value))
}
Promise.reject()
Promise.reject()
Метод возвращает объект Promise с указанием причины отклонения.
//静态的reject方法
static reject(reason) {
return new MyPromise((resolve, reject) => reject(reason))
}
Promise.all()
Promise.all(iterable)
Метод возвращает экземпляр промиса, обратный вызов этого экземпляра завершается (разрешается), когда все промисы в итерируемом параметре «разрешены» или параметр не содержит промиса; если одно из промисов в параметре терпит неудачу (отклоняется), этот экземпляр callback терпит неудачу (отклонение), причина отказа — результат первого невыполненного обещания.
//静态的all方法
static all(promiseArr) {
let index = 0
let result = []
return new MyPromise((resolve, reject) => {
promiseArr.forEach((p, i) => {
//Promise.resolve(p)用于处理传入值不为Promise的情况
MyPromise.resolve(p).then(
val => {
index++
result[i] = val
//所有then执行后, resolve结果
if(index === promiseArr.length) {
resolve(result)
}
},
err => {
//有一个Promise被reject时,MyPromise的状态变为reject
reject(err)
}
)
})
})
}
Promise.race()
Promise.race(iterable)
Метод возвращает обещание, которое разрешается или отклоняется после разрешения или отклонения обещания в итераторе.
static race(promiseArr) {
return new MyPromise((resolve, reject) => {
//同时执行Promise,如果有一个Promise的状态发生改变,就变更新MyPromise的状态
for (let p of promiseArr) {
MyPromise.resolve(p).then( //Promise.resolve(p)用于处理传入值不为Promise的情况
value => {
resolve(value) //注意这个resolve是上边new MyPromise的
},
err => {
reject(err)
}
)
}
})
}
полный код
//Promise/A+规定的三种状态
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'
class MyPromise {
// 构造方法接收一个回调
constructor(executor) {
this._status = PENDING // Promise状态
this._value = undefined // 储存then回调return的值
this._resolveQueue = [] // 成功队列, resolve时触发
this._rejectQueue = [] // 失败队列, reject时触发
// 由于resolve/reject是在executor内部被调用, 因此需要使用箭头函数固定this指向, 否则找不到this._resolveQueue
let _resolve = (val) => {
//把resolve执行回调的操作封装成一个函数,放进setTimeout里,以兼容executor是同步代码的情况
const run = () => {
if(this._status !== PENDING) return // 对应规范中的"状态只能由pending到fulfilled或rejected"
this._status = FULFILLED // 变更状态
this._value = val // 储存当前value
// 这里之所以使用一个队列来储存回调,是为了实现规范要求的 "then 方法可以被同一个 promise 调用多次"
// 如果使用一个变量而非队列来储存回调,那么即使多次p1.then()也只会执行一次回调
while(this._resolveQueue.length) {
const callback = this._resolveQueue.shift()
callback(val)
}
}
setTimeout(run)
}
// 实现同resolve
let _reject = (val) => {
const run = () => {
if(this._status !== PENDING) return // 对应规范中的"状态只能由pending到fulfilled或rejected"
this._status = REJECTED // 变更状态
this._value = val // 储存当前value
while(this._rejectQueue.length) {
const callback = this._rejectQueue.shift()
callback(val)
}
}
setTimeout(run)
}
// new Promise()时立即执行executor,并传入resolve和reject
executor(_resolve, _reject)
}
// then方法,接收一个成功的回调和一个失败的回调
then(resolveFn, rejectFn) {
// 根据规范,如果then的参数不是function,则我们需要忽略它, 让链式调用继续往下执行
typeof resolveFn !== 'function' ? resolveFn = value => value : null
typeof rejectFn !== 'function' ? rejectFn = reason => {
throw new Error(reason instanceof Error? reason.message:reason);
} : null
// return一个新的promise
return new MyPromise((resolve, reject) => {
// 把resolveFn重新包装一下,再push进resolve执行队列,这是为了能够获取回调的返回值进行分类讨论
const fulfilledFn = value => {
try {
// 执行第一个(当前的)Promise的成功回调,并获取返回值
let x = resolveFn(value)
// 分类讨论返回值,如果是Promise,那么等待Promise状态变更,否则直接resolve
x instanceof MyPromise ? x.then(resolve, reject) : resolve(x)
} catch (error) {
reject(error)
}
}
// reject同理
const rejectedFn = error => {
try {
let x = rejectFn(error)
x instanceof MyPromise ? x.then(resolve, reject) : resolve(x)
} catch (error) {
reject(error)
}
}
switch (this._status) {
// 当状态为pending时,把then回调push进resolve/reject执行队列,等待执行
case PENDING:
this._resolveQueue.push(fulfilledFn)
this._rejectQueue.push(rejectedFn)
break;
// 当状态已经变为resolve/reject时,直接执行then回调
case FULFILLED:
fulfilledFn(this._value) // this._value是上一个then回调return的值(见完整版代码)
break;
case REJECTED:
rejectedFn(this._value)
break;
}
})
}
//catch方法其实就是执行一下then的第二个回调
catch(rejectFn) {
return this.then(undefined, rejectFn)
}
//finally方法
finally(callback) {
return this.then(
value => MyPromise.resolve(callback()).then(() => value), //执行回调,并returnvalue传递给后面的then
reason => MyPromise.resolve(callback()).then(() => { throw reason }) //reject同理
)
}
//静态的resolve方法
static resolve(value) {
if(value instanceof MyPromise) return value //根据规范, 如果参数是Promise实例, 直接return这个实例
return new MyPromise(resolve => resolve(value))
}
//静态的reject方法
static reject(reason) {
return new MyPromise((resolve, reject) => reject(reason))
}
//静态的all方法
static all(promiseArr) {
let index = 0
let result = []
return new MyPromise((resolve, reject) => {
promiseArr.forEach((p, i) => {
//Promise.resolve(p)用于处理传入值不为Promise的情况
MyPromise.resolve(p).then(
val => {
index++
result[i] = val
if(index === promiseArr.length) {
resolve(result)
}
},
err => {
reject(err)
}
)
})
})
}
//静态的race方法
static race(promiseArr) {
return new MyPromise((resolve, reject) => {
//同时执行Promise,如果有一个Promise的状态发生改变,就变更新MyPromise的状态
for (let p of promiseArr) {
MyPromise.resolve(p).then( //Promise.resolve(p)用于处理传入值不为Promise的情况
value => {
resolve(value) //注意这个resolve是上边new MyPromise的
},
err => {
reject(err)
}
)
}
})
}
}
С более чем 150 строками кода мы наконец-то можем положить конец реализации Promise. Мы начали с простейшего примера использования промиса, через анализ вызывающего процесса реализовали общий скелет промиса по режиму наблюдателя, а затем наполнили код по спецификации промиса/А+, сосредоточившись на реализации цепного вызова затем и, наконец, завершены статические/экземплярные методы для промисов. На самом деле общая концепция реализации Promise не слишком сложна, но мы часто игнорируем многие детали Promise в нашем повседневном использовании, поэтому сложно написать реализацию Promise, которая соответствует спецификации.
асинхронная/ожидающая реализация
Хоть я и потратил так много времени на разговоры о реализации Promise, но изучаяasync/await
Механизм приостановки выполнения является нашим первоначальным намерением, и мы введем содержание этого раздела ниже. Точно так же давайте начнем с определения значения async/await.
В сценариях, зависящих от нескольких обратных вызовов, хотя Promise заменяет вложение обратных вызовов цепными вызовами, читаемость слишком большого количества связанных вызовов по-прежнему неудовлетворительна, а управление потоком неудобно.Асинхронная функция, предложенная ES7, наконец, делает JS. асинхронные операции, которые лаконично и красиво решают две вышеупомянутые проблемы.
Представьте себе сценарий, в котором есть зависимости между асинхронными задачами a->b->c.Если мы будем обрабатывать эти отношения через цепочку вызовов then, читабельность будет не очень хорошей.Если мы хотим контролировать один из процессов, например, при определенных условиях, b не спускается к c, поэтому управлять им не очень удобно.
Promise.resolve(a)
.then(b => {
// do something
})
.then(c => {
// do something
})
Но если этот сценарий реализовать через async/await, читабельность и управление потоком будет гораздо удобнее.
async () => {
const a = await Promise.resolve(a);
const b = await Promise.resolve(b);
const c = await Promise.resolve(c);
}
Итак, как нам реализовать async/await, в первую очередь нам нужно знать,async/await на самом деле является инкапсуляцией Generator (генератора), является синтаксическим сахаром. Поскольку генератор был заменен на async/await вскоре после его появления, многие студенты относительно незнакомы с генератором, поэтому давайте посмотрим на использование генератора:
ES6 недавно представила функцию Generator, которая может приостанавливать поток выполнения функции с помощью ключевого слова yield и переключаться в следующее состояние с помощью метода next(), что дает возможность изменить поток выполнения, тем самым обеспечивая решение для асинхронных программирование.
function* myGenerator() {
yield '1'
yield '2'
return '3'
}
const gen = myGenerator(); // 获取迭代器
gen.next() //{value: "1", done: false}
gen.next() //{value: "2", done: false}
gen.next() //{value: "3", done: true}
также даваяnext()
Передайте параметры, пусть yield имеет возвращаемое значение
function* myGenerator() {
console.log(yield '1') //test1
console.log(yield '2') //test2
console.log(yield '3') //test3
}
// 获取迭代器
const gen = myGenerator();
gen.next()
gen.next('test1')
gen.next('test2')
gen.next('test3')
Когда мы видим использование Generator, оно должно казаться очень знакомым,*/yield
иasync/await
Выглядит очень похоже, они оба предоставляют функцию приостановки выполнения, но между ними есть три отличия:
-
async/await
С собственным исполнителем он может автоматически выполнять следующий шаг без ручного вызова next(). -
async
Возвращаемое значение функции является объектом Promise, а генератор возвращает объект генератора. -
await
Возможность вернуть значение разрешения/отклонения промиса
Наша реализация async/await, по сути, заключается в инкапсуляции генератора, соответствующего трем вышеуказанным пунктам.
1. Автоматическое выполнение
Давайте сначала посмотрим, для такого Генератора, каков процесс ручного выполнения
function* myGenerator() {
yield Promise.resolve(1);
yield Promise.resolve(2);
yield Promise.resolve(3);
}
// 手动执行迭代器
const gen = myGenerator()
gen.next().value.then(val => {
console.log(val)
gen.next().value.then(val => {
console.log(val)
gen.next().value.then(val => {
console.log(val)
})
})
})
//输出1 2 3
Мы также можем датьgen.next()
Способ передачи значения, чтобы yield мог вернуть значение разрешения
function* myGenerator() {
console.log(yield Promise.resolve(1)) //1
console.log(yield Promise.resolve(2)) //2
console.log(yield Promise.resolve(3)) //3
}
// 手动执行迭代器
const gen = myGenerator()
gen.next().value.then(val => {
// console.log(val)
gen.next(val).value.then(val => {
// console.log(val)
gen.next(val).value.then(val => {
// console.log(val)
gen.next(val)
})
})
})
Очевидно, что ручное выполнение выглядит коряво и некрасиво. Мы надеемся, что функция генератора может выполняться автоматически, а yield может возвращать значение разрешения. Исходя из этих двух требований, мы делаем базовый пакет, здесьasync/await
является ключевым словом и не может быть переопределено. Мы имитируем его с помощью функции:
function run(gen) {
var g = gen() //由于每次gen()获取到的都是最新的迭代器,因此获取迭代器操作要放在_next()之前,否则会进入死循环
function _next(val) { //封装一个方法, 递归执行g.next()
var res = g.next(val) //获取迭代器对象,并返回resolve的值
if(res.done) return res.value //递归终止条件
res.value.then(val => { //Promise的then方法是实现自动迭代的前提
_next(val) //等待Promise完成就自动执行下一个next,并传入resolve的值
})
}
_next() //第一次执行
}
Для нашего предыдущего примера мы могли бы сделать это так:
function* myGenerator() {
console.log(yield Promise.resolve(1)) //1
console.log(yield Promise.resolve(2)) //2
console.log(yield Promise.resolve(3)) //3
}
run(myGenerator)
Таким образом, мы изначально реализовалиasync/await
. Приведенный выше код состоит всего из пяти или шести строк, но его нелегко понять с первого взгляда Мы использовали четыре примера ранее в качестве предзнаменования, что также позволяет читателям лучше понять этот код. Проще говоря, мы инкапсулируем метод запуска В методе запуска мы инкапсулируем следующую операцию как_next()
, каждый раз, когда выполняется Promise.then()_next()
, чтобы добиться эффекта автоматической итерации. В процессе итерации мы также передаем значение разрешенияgen.next()
, так что yield может вернуть значение разрешения обещания
Вставьте предложение здесь, это только
.then方法
Может ли эта форма завершить функции, которые мы автоматически выполняем? Ответ — нет, помимо промисов можно подключиться и к yield.thunk函数
, функция thunk не нова, так называемая функция thunkФункция с одним аргументом, которая принимает только обратные вызовы, подробнее см.Значение и использование функции Thunk Руан Ифэн, будь то Promise или функция thunk, ее ядро проходит черезвходящий обратный вызовспособ реализовать автоматическое выполнение генератора. Функция thunk используется только как расширение знаний, и учащиеся, испытывающие трудности с пониманием, также могут ее пропустить, и это не влияет на последующее понимание.
2. Обещание возврата и обработка исключений
Хотя мы реализовали автоматическое выполнение Generator и позволили yield возвращать значение разрешения, все еще есть несколько проблем с приведенным выше кодом:
-
Требуется совместимый базовый тип: Предпосылка, что этот код может выполняться автоматически,
yield
После обещания, чтобы быть совместимым со случаем значений примитивного типа, нам нужно поместить содержимое yield (gen().next.value
) использовать обаPromise.resolve()
конвертировать один раз -
Отсутствует обработка ошибок: Если обещание в приведенном выше коде не выполняется, это приведет к прямому прерыванию последующего выполнения.Нам нужно вызвать
Generator.prototype.throw()
, сгенерировать ошибку до того, как она будет перехвачена внешней функцией try-catch. -
Возвращаемое значение является обещанием:
async/await
Возвращаемое значение — это обещание, здесь мы также должны быть последовательны и упаковать обещание для возвращаемого значения.
Давайте изменим метод запуска:
function run(gen) {
//把返回值包装成promise
return new Promise((resolve, reject) => {
var g = gen()
function _next(val) {
//错误处理
try {
var res = g.next(val)
} catch(err) {
return reject(err);
}
if(res.done) {
return resolve(res.value);
}
//res.value包装为promise,以兼容yield后面跟基本类型的情况
Promise.resolve(res.value).then(
val => {
_next(val);
},
err => {
//抛出错误
g.throw(err)
});
}
_next();
});
}
Затем мы можем проверить это:
function* myGenerator() {
try {
console.log(yield Promise.resolve(1))
console.log(yield 2) //2
console.log(yield Promise.reject('error'))
} catch (error) {
console.log(error)
}
}
const result = run(myGenerator) //result是一个Promise
//输出 1 2 error
здесьasync/await
Реализация в основном завершена. Наконец, мы можем посмотреть на результат преобразования babel в async/await, по сути, общая идея та же, но метод записи немного отличается:
//相当于我们的run()
function _asyncToGenerator(fn) {
// return一个function,和async保持一致。我们的run直接执行了Generator,其实是不太规范的
return function() {
var self = this
var args = arguments
return new Promise(function(resolve, reject) {
var gen = fn.apply(self, args);
//相当于我们的_next()
function _next(value) {
asyncGeneratorStep(gen, resolve, reject, _next, _throw, 'next', value);
}
//处理异常
function _throw(err) {
asyncGeneratorStep(gen, resolve, reject, _next, _throw, 'throw', err);
}
_next(undefined);
});
};
}
function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) {
try {
var info = gen[key](arg);
var value = info.value;
} catch (error) {
reject(error);
return;
}
if (info.done) {
resolve(value);
} else {
Promise.resolve(value).then(_next, _throw);
}
}
Как пользоваться:
const foo = _asyncToGenerator(function* () {
try {
console.log(yield Promise.resolve(1)) //1
console.log(yield 2) //2
return '3'
} catch (error) {
console.log(error)
}
})
foo().then(res => {
console.log(res) //3
})
Связанныйasync/await
На этом осознание подходит к концу. Но до конца мы не знаем, как await приостанавливает выполнение.Чтобы узнать секрет ожидания приостановки выполнения, нам нужно перейти к реализации генератора, чтобы найти ответ
Реализация генератора
Давайте начнем с простого примера использования Генератора и шаг за шагом рассмотрим принцип реализации Генератора:
function* foo() {
yield 'result1'
yield 'result2'
yield 'result3'
}
const gen = foo()
console.log(gen.next().value)
console.log(gen.next().value)
console.log(gen.next().value)
мы можемофициальный сайт бабельПреобразуйте этот код онлайн, чтобы увидеть, как Generator реализован в среде ES5:
"use strict";
var _marked =
/*#__PURE__*/
regeneratorRuntime.mark(foo);
function foo() {
return regeneratorRuntime.wrap(function foo$(_context) {
while (1) {
switch (_context.prev = _context.next) {
case 0:
_context.next = 2;
return 'result1';
case 2:
_context.next = 4;
return 'result2';
case 4:
_context.next = 6;
return 'result3';
case 6:
case "end":
return _context.stop();
}
}
}, _marked);
}
var gen = foo();
console.log(gen.next().value);
console.log(gen.next().value);
console.log(gen.next().value);
Код на первый взгляд не длинный, но если приглядеться, то обнаружишь две вещи, о которых не знаешь -regeneratorRuntime.mark
иregeneratorRuntime.wrap
, эти два метода на самом деле являются двумя методами в модуле regenerator-runtime. Модуль regenerator-runtime взят из модуля regenerator facebook. Полный код находится вruntime.js, эта среда выполнения имеет более 700 строк...-_-||, поэтому мы не можем говорить обо всем, мы кратко пробежимся по менее важным частям, сосредоточив внимание на приостановке выполнения соответствующих частей кода.
Лично я считаю, что эффект от прогрызания исходников не очень хороший.Рекомендуется читателям дотянуть до конца, чтобы увидеть вывод и сокращенную версию реализации.Исходники используются как дополнительное понимание.
regeneratorRuntime.mark()
regeneratorRuntime.mark(foo)
Этот метод вызывается в первой строке, давайте посмотрим на определение метода mark() во время выполнения.
//runtime.js里的定义稍有不同,多了一些判断,以下是编译后的代码
runtime.mark = function(genFun) {
genFun.__proto__ = GeneratorFunctionPrototype;
genFun.prototype = Object.create(Gp);
return genFun;
};
сюдаGeneratorFunctionPrototype
иGp
Мы не знаем друг друга, они определены в рантайме, но это не важно, нам просто нужно знатьmark()方法
Достаточно привязать серию прототипов к функции-генератору (foo), что здесь просто
regeneratorRuntime.wrap()
Из приведенного выше кода, преобразованного babel, мы видим, что выполнениеfoo()
, по сути, заключается в выполненииwrap()
, то что делает этот метод?Что он хочет обернуть?Давайте посмотрим на определение метода обертывания:
//runtime.js里的定义稍有不同,多了一些判断,以下是编译后的代码
function wrap(innerFn, outerFn, self) {
var generator = Object.create(outerFn.prototype);
var context = new Context([]);
generator._invoke = makeInvokeMethod(innerFn, self, context);
return generator;
}
Метод wrap сначала создает генератор и наследуетouterFn.prototype
; потом новыйcontext对象
;makeInvokeMethod方法
перениматьinnerFn(对应foo$)
,context
иthis
, и подключите возвращаемое значение кgenerator._invoke
Включите, наконец, верните генератор.По сути, wrap() эквивалентен добавлению в генератор метода _invoke.
Этот код должен вызывать много вопросов: что такое externalFn.prototype, что такое Context и что делает makeInvokeMethod. Давайте ответим на них по очереди:
outerFn.prototype
На самом деле этоgenFun.prototype
,
Это мы можем узнать, объединив приведенный выше код
context
Непосредственно под ним можно понимать такой глобальный объект для хранения различных состояний и контекстов:
var ContinueSentinel = {};
var context = {
done: false,
method: "next",
next: 0,
prev: 0,
abrupt: function(type, arg) {
var record = {};
record.type = type;
record.arg = arg;
return this.complete(record);
},
complete: function(record, afterLoc) {
if (record.type === "return") {
this.rval = this.arg = record.arg;
this.method = "return";
this.next = "end";
}
return ContinueSentinel;
},
stop: function() {
this.done = true;
return this.rval;
}
};
makeInvokeMethod
определяется следующим образом, он возвращаетinvoke方法
, Invoke используется для оценки текущего состояния и выполнения следующего шага, который на самом деле называетсяnext()
//以下是编译后的代码
function makeInvokeMethod(innerFn, context) {
// 将状态置为start
var state = "start";
return function invoke(method, arg) {
// 已完成
if (state === "completed") {
return { value: undefined, done: true };
}
context.method = method;
context.arg = arg;
// 执行中
while (true) {
state = "executing";
var record = {
type: "normal",
arg: innerFn.call(self, context) // 执行下一步,并获取状态(其实就是switch里边return的值)
};
if (record.type === "normal") {
// 判断是否已经执行完成
state = context.done ? "completed" : "yield";
// ContinueSentinel其实是一个空对象,record.arg === {}则跳过return进入下一个循环
// 什么时候record.arg会为空对象呢, 答案是没有后续yield语句或已经return的时候,也就是switch返回了空值的情况(跟着上面的switch走一下就知道了)
if (record.arg === ContinueSentinel) {
continue;
}
// next()的返回值
return {
value: record.arg,
done: context.done
};
}
}
};
}
Почему
generator._invoke
фактическиgen.next
Это связано с тем, что среда выполнения для определения next(), метод next() фактически возвращает _invoke
// Helper for defining the .next, .throw, and .return methods of the
// Iterator interface in terms of a single ._invoke method.
function defineIteratorMethods(prototype) {
["next", "throw", "return"].forEach(function(method) {
prototype[method] = function(arg) {
return this._invoke(method, arg);
};
});
}
defineIteratorMethods(Gp);
Реализация низкой конфигурации и анализ потока вызовов
По оценкам, после прочтения исходного кода таким образом многие читатели все еще находятся в замешательстве.В конце концов, в исходном коде есть много понятий и пакетов, которые могут быть не полностью поняты какое-то время.Давайте выпрыгнем из исходного кода. код и реализовать простой генератор, а затем вернуться к исходному коду, чтобы получить более четкое представление
// 生成器函数根据yield语句将代码分割为switch-case块,后续通过切换_context.prev和_context.next来分别执行各个case
function gen$(_context) {
while (1) {
switch (_context.prev = _context.next) {
case 0:
_context.next = 2;
return 'result1';
case 2:
_context.next = 4;
return 'result2';
case 4:
_context.next = 6;
return 'result3';
case 6:
case "end":
return _context.stop();
}
}
}
// 低配版context
var context = {
next:0,
prev: 0,
done: false,
stop: function stop () {
this.done = true
}
}
// 低配版invoke
let gen = function() {
return {
next: function() {
value = context.done ? undefined: gen$(context)
done = context.done
return {
value,
done
}
}
}
}
// 测试使用
var g = gen()
g.next() // {value: "result1", done: false}
g.next() // {value: "result2", done: false}
g.next() // {value: "result3", done: false}
g.next() // {value: undefined, done: true}
Этот код несложно понять, давайте разберем процесс вызова:
- мы определяем
function*
Функция генератора преобразуется в приведенный выше код - Преобразованный код разбит на три блока:
-
gen$(_context)
Из кода функции генератора разделения урожая -
context对象
Используется для хранения контекста выполнения функции -
invoke()方法
Определите next(), который используется для выполнения gen$(_context) для перехода к следующему шагу.
-
- когда мы звоним
g.next()
, что эквивалентно вызовуinvoke()方法
,воплощать в жизньgen$(_context)
, введите оператор switch, переключатель выполняет соответствующий блок case в соответствии с идентификатором контекста и возвращает соответствующий результат - Когда функция генератора работает до конца (следующего выхода нет или он уже вернулся), если переключатель не может сопоставить соответствующий блок кода, он вернет нулевое значение.
g.next()
возвращение{value: undefined, done: true}
Отсюда мы видим, чтоСуть реализации Генератора заключается в上下文的保存
, функция на самом деле не приостанавливается, каждый yield фактически выполняет входящую функцию генератора один раз, но объект контекста используется для хранения контекста в середине процесса, так что каждый раз, когда функция генератора выполняется, может быть выполнено из предыдущий результат выполнения, похоже, что функция приостановлена
Резюме и благодарность
Принципы Promise, async/await и Generator были реализованы здесь. Спасибо, что смогли пройти со мной через весь процесс. Прежде чем мы узнали об этом, мы потратили почти 9000 слов, рассказывая истории об асинхронном программировании, мире асинхронного В начале автору было просто интересно узнать о механизме приостановки await, а позже из небольшого вопроса "как в await реализуется приостановка выполнения" был почерпнут ряд принципов мышления и реализации асинхронного программирования. Реализация этих трех на самом деле является процессом пошаговой эволюции внешнего асинхронного программирования.
В процессе написания этой статьи мне помогали многие начальники.Эти четыре справочные статьи выбраны мной после прочтения большого количества связанных статей.Рекомендуется прочитать их вместе.Начальники написали намного лучше чем я. Кроме того, спасибо Ю Ю. Объяснение, данное парнем по механизму Генератора~
Мастер фронтенда:Различные реализации исходного кода, все, что вам нужно, здесь
Бог Троицы:Как реализовать промисы
зимний:Принцип асинхронности/ожидания и анализ последовательности выполнения
Сан Ю:Как выглядит Babel из серии ES6, компилирующий Generator
Наконец скромный лайк спасибо♪(・ω・)ノ
Прошлые статьи
- 10 строк кода, чтобы увидеть реализацию redux — всесторонний анализ проектирования и реализации промежуточного программного обеспечения redux, react-redux и redux | 8k слов
- Красные и черные плоды на красно-черном дереве, ты и я под красно-черным деревом - Знакомство с красно-черным деревом | 6k слов
- SSR от входа до выхода — подробный принцип рендеринга React на стороне сервера | 1W word?