написать впереди
Прошло два месяца с моей последней статьи.За последние два месяца, из-за того, что я был занят собеседованиями на стажировку, производство статьи было немного задержано (на самом деле лень), и, наконец, я успешно получил определенное предложение от большой фабрики. сейчас поступил на стажировку, что является первой хорошей новостью в 2021 году (смеется). Ладно, нечего сказать, продолжим предыдущую статьюРеализовать обещание, соответствующее спецификации Promise/A+ (версия машинописного текста).. На этот раз мы реализуем машинописную версию async/await.
Есть много статей о принципе async/await, но, поскольку эта статья написана на машинописном языке, наш async/await должен уметь автоматически выводить результат через функцию, переданную пользователем, поэтомуКак написать определение машинописного текста для него, также является важным разделом этой статьи.
Что такое асинхронный/ожидающий
По словам Красной книги, «async/await» на самом деле является асинхронной функцией, которая является применением Promise в функциях, новой спецификации в ES2017 (ES8).Эта новая функция позволяет коду, написанному синхронно, выполняться асинхронно..
Основное использование
Конкретное использование не будет здесь повторяться, но общее использование выглядит следующим образом:
// success
async function fn1() {
/*
如果 await 一个 Promise,成功时可以直接将 Promise 的 fulfilled 的值取出
如果是一个非 Promise 的值,await 可以看作不存在,不会有任何实际的作用。
*/
const res = await new Promise<string>((resolve, reject) => {
setTimeout(() => {
resolve('fulfilled')
}, 2000)
})
console.log(res) // fulfilled
return res
}
// error
async function fn2() {
try {
/*
如果 await 的 Promise 的状态为 rejected,那么就会抛出一个同步的错误,
该错误即使不 try/catch 也不会阻止程序正常运行,因为 async 本质也是
在函数外部套了一层 Promise,会直接触发 UnhandledPromiseRejectionWarning
*/
const res = await new Promise<string>((resolve, reject) => {
setTimeout(() => {
reject('rejected')
}, 2000)
})
return res
} catch (error) {
console.log(error) // rejected
return Promise.reject(error)
}
}
Что касается возвращаемого значения функции async/await, давайте поговорим об этом здесь. Как упоминалось ранее,async обернет слой Promise для всей функции, поэтому, когда возвращаемое значение внутри функции эквивалентно обещаниюthenВозвращаемое значение в обратном вызове, возвращаемое значение будет обернуто Promise.
Природа
Хорошо. Я думаю, что вы должны быть знакомы с основным использованием async/await, и вы должны были вообще слышать о его сути.В конечном счете, эта функция является синтаксическим сахаром, который дополняется функцией Generator, предложенной в ES6 и Обещание Обещание Я полагаю, что все знакомы с этим, и функция Generator вернет интерфейс Iterator, так что же такое функция Generator и интерфейс Iterator? Давайте обсудим это позже.
Iterator
Итератор (итератор) - это интерфейс или вид механизма. Он обеспечивает унифицированный механизм доступа для различных структур данных, любых структур данных до тех пор, пока интерфейс итератора развертывания вы можете завершить операцию обхода (которая, в свою очередь, справиться со всеми членами структуры данных). Основная роль состоит в том, чтобы обеспечить единую структуру для легкого доступа к различным интерфейсам данных, структурах данных, так что элементы могут быть организованы в определенном порядке.
Итератор по сути является объектом-указателем, и процесс его реализации выглядит следующим образом:
- Создает объект-указатель, указывающий на начало текущей структуры данных.
- Первый вызов объекта указателя
nextметод, вы можете указать указатель на первый член структуры данных. - Второй вызов объекта указателя
nextметод указатель указывает на второй член структуры данных. - Постоянный вызов объекта указателя
nextметод, пока он не укажет на конец структуры данных.
Итератор в родной структуре данных
В JS есть некоторые встроенные структуры данных, которые по своей природе имеют интерфейс Iterator по умолчанию, в том числе:
- Array
- Map
- Set
- String
- объект аргументов функции
- Объект NodeList
Чтобы получить интерфейс Iterator в этих структурах данных, вам нужно вызватьSymbol.iteratorметод:
// 数组的Symbol.iterator方法
const arr = ['a', 'b', 'c'];
const iter = arr[Symbol.iterator]();
// 通过next()方法实现每一次的迭代器的遍历
iter.next() // { value: 'a', done: false }
iter.next() // { value: 'b', done: false }
iter.next() // { value: 'c', done: false }
iter.next() // { value: undefined, done: true }
вvalueзначение, проходимое каждый раз,doneУказывает, следует ли полностью обходить массив.
Пользовательский итератор
Как упоминалось ранее, Iterator предназначен только для предоставления нам унифицированного интерфейса доступа, поэтому любой обычный объект может реализовать Iterator, просто нужно определитьSymbol.iteratorметод подойдет.
const iterObj = {
value: 0,
[Symbol.iterator]() {
const self = this
return {
next() {
const value = self.value++
const done = value > 2
return {
value: done ? undefined : value,
done
}
}
}
}
}
const iter = iterObj[Symbol.iterator]()
iter.next() // { value: 0, done: false }
iter.next() // { value: 1, done: false }
iter.next() // { value: 2, done: false }
iter.next() // { value: undefined, done: true }
iter.next() // { value: undefined, done: true }
Интерфейс Iterator, который мы реализуем, должен иметьnextметод (этот метод будетfor...ofвызывается в такой инструкции), в то время как вnextВ методе необходимо судить о завершенном состоянии и незавершенном состоянии.Когда все итерации завершены,doneУстановить какtrue, который также возвращаетvalueдолжно бытьundefined.
В этой статье нам нужно только понять основные концепции Iterator, Если вы хотите узнать об этом больше, вы можете перейти кMDNПосмотреть выше.
Другие методы
В дополнение к итераторам должны быть реализованыnextметод, есть также два необязательных метода, которыеreturnа такжеthrowметод.
Метод возврата
returnМетод для выполнения указанного логического итератора при отключении продвижения. Когда мы не хотим проходить по итерируемому объекту, он может быть «закрыт». Возможные сценарии:
-
for...ofцикл черезbreak,continue,returnилиthrowВыйти рано. - Операция деструктуризации не использует все значения.
Например, при вызове в объекте итератора, возвращаемом функцией Generator.returnспособ закрыть его раньше:
function* gen() {
yield 1;
yield 2;
yield 3;
}
const g = gen();
console.log(g.next());// { value: 1, done: false }
// 同时该方法还可传递参数,而向一般的迭代器的 return 方法传入参数是没有用的
console.log(g.return('foo'))// { value: "foo", done: true }
console.log(g.next());// { value: undefined, done: true }
можно найти, звонитеreturnПосле метода итератор уже находится в завершенном состоянии.
Стоит отметить, что поскольку этот метод является необязательным, не все итераторы можно закрыть, например, итераторы для массивов:
const a = [1, 2, 3, 4, 5]
const iter = a[Symbol.iterator]()
for (const i of iter) {
console.log(i)
if(i > 2) {
break
}
}
// 1
// 2
// 3
for (const i of iter) {
console.log(i)
}
// 4
// 5
Конечно, этот метод фактически не используется в этой статье и рассматривается здесь как дополнительное расширение.
метод броска
throwМетод в основном используется в связке с функцией Генератор.Общие объекты-итераторы не используют этот метод.Подробнее о нем мы поговорим далее в Генераторе.
Generator
Генератор – это новый тип данных, представленный в ES6. По сути, это реализация сопрограмм JS. Что касается сопрограмм, я не буду здесь больше углубляться. Продолжайте изучать движок JS. Заинтересованные партнеры могут проверить информацию на их.
Отличия от обычных функций
- При объявлении функции
functionМежду ключевым словом и названием функции стоит звездочка. При этом стрелочные функции нельзя использовать для объявления, иначе будет сообщено об ошибке. - Возвращаемое значение функции Generator отличается от обычной функции, но возвращает объект итератора, который может поочередно перебирать каждое состояние внутри функции Generator.
- Используется внутри тела функции
yieldВыражения, определяющие различные внутренние состояния. - Функции генератора не могут быть использованы
newключевое слово, иначе будет сообщено об ошибке.
как использовать
Поскольку основная проблема, обсуждаемая в этой статье, заключается в том, как реализовать async/await, использование функции Generator знакомит только с ее основным использованием и частями, связанными с реализацией async/await.
function* helloWorldGenerator() {
yield 'hello';
yield 'world';
return 'ending';
}
const hw = helloWorldGenerator();
console.log(hw);
console.log(hw.next());// {value: "hello", done: false}
console.log(hw.next());// {value: "world", done: false}
console.log(hw.next());// {value: "ending", done: true}
console.log(hw.next());// {value: undefined, done: true}
Объяснение приведенного выше кода:
- После вызова функции генератора функция не будет запущена и не вернет текущий результат функции, а возвращенныйИтераторобъект, внутренний
yieldВыражение является состоянием, и значение после выражения будет использоваться как возвращаемое значение состояния, поэтому функция имеет три состояния:hello,worldа такжеretrunСтатус окончания выполнения оператораending - Если вы хотите запустить каждый генератор внутри функции Generator
yieldэтап, необходимо вызвать итераторnext()Способ перемещения указателя состояния внутри его функции в следующее состояние, каждый раз, когда он называетсяnext()метод, внутренний указатель начнет выполнение с того места, где остановился заголовок функции или предыдущий слой, пока он не будет перемещен на следующийyiedlвыражение или встречаreturnзаявление (встречаетсяreturnоператорная функция остановится напрямую), все еще может быть вызвана после остановкиnext()метод, но возвращаемыйvalueдляundefined.
yield
В выражении yield нет ничего особенного, оно просто представляет собой знак паузы, а значение после выражения yield эквивалентно значению этапа, который будет использоваться как соответствующее значение вызова.next()пост-объектvalueСтоимость имущества.
Параметр метода next() итератора
yieldСамого возвращаемого значения нет, или оно всегда возвращаетundefined. Итератор, возвращаемый функцией GeneratornextМетод может принимать один параметр, и этот параметр будет считаться предыдущим.yieldВозвращаемое значение оператора.
function* f() {
const a = yield 1
console.log(a) // 'a'
const b = yield 2
console.log(b) // 'b'
}
const g = f()
g.next() // { value: 1, done: false }
g.next('a') // { value: 1, done: false }
g.next('b') // { value: undefined, done: true }
При передаче параметров мы должны начать со второгоnextметод начинает проходить, потому что первыйnextМетод запускается изнутри функции, и нет фронта.yieldвыражение, поэтому первоеnextПараметры в методе не имеют никакого эффекта.Если вы хотите передать параметры в начале, вы должны передать параметры функции-генератора при создании объекта-итератора.
function* f(name:string) {
const a = yield name
console.log(a) // 'a'
const b = yield 2
console.log(b) // 'b'
}
const g = f('Coloring')
g.next() // { value: 'Coloring', done: false }
Поскольку функция генератора возобновляется из состояния паузы, ее контекстное состояние (контекст) не изменяется. пройти черезnextпараметров метода, есть способ продолжить ввод значений в тело функции после того, как функция Генератор запустится. То есть вы можете настроить поведение функции, вводя разные значения извне внутрь на разных этапах выполнения функции-генератора.
Метод throw() итератора
Как мы упоминали ранее, итератор, сгенерированный функцией Generator, имеет третий методthrow, этот метод аналогиченreturnЭтим же методом можно принудительно закрыть генератор.
function* generatorFn() {
for (const x of [1, 2, 3]) {
yield x
}
}
const g = generatorFn()
console.log(g) // generatorFn {<suspended>}
try {
// 未处理会抛出同步错误
g.throw('foo')
} catch (error) {
console.log(error) // foo
}
console.log(g) // generatorFn {<closed>}
Но если мы обработаем ошибку внутри функции Generator, генератор не выключится и выполнение может возобновиться. ошибки будут пропускать соответствующиеyield,следующим образом:
function* generatorFn() {
for (const x of [1, 2, 3]) {
try {
yield x
} catch (error) {
console.log(error) // foo
}
}
}
const g = generatorFn()
console.log(g.next()) // { value: 1, done: false }
g.throw('foo')
console.log(g.next()) // { value: 3, done: false }
Я полагаю, вы можете догадаться, что мы можем использовать этот метод для имитации исключения синхронного броска async/await, а затем мы можем получить его, получивrejectedСтатус обещанийreasonВыкинуть вручную.
Реализовать асинхронность/ожидание
Так много было сказано ранее, на самом деле это подготовка к реализации async/await, Из функции генератора мы можем узнать, как ее использовать.yieldЕго можно использовать как признак приостановки функции, но каждый раз при продолжении выполнения нужно вручную вызывать итераторnextметод, а async/await, по сути, упрощает этот метод ручного вызова, чтобы функция Generator могла автоматически выполнять итерацию.
Когда мы ранее использовали async/await, мы обнаружили, чтоawaitзначение сразу после илиfulfilledЗначение обещания состояния напрямую используется как возвращаемое значение этого выражения. Тогда, пока мы можемyieldЗначение, следующее непосредственно за ним, также используется какyieldВозвращаемое значение выражения и возвращаемое значение функции Generator также обернуты Promise, тогда можно ли успешно реализовать async/await?
На самом деле, некоторые люди уже реализовали соответствующие библиотеки, основанные на этом принципе, например:co, то мы также можем сделать его простую реализацию в свою очередь.
Реализовать функцию-оболочку
Функция-оболочка запустит входящую функцию обратного вызова Generator, получит сгенерированный ею объект итератора, вызовет внутренний итератор и вернет обещание, привязанное к каждому шагу итератора.
Введите запрос
Давайте сначала посмотрим, как выглядят определения типов при использовании функций async/await:
async function fun(a: string) {
return a
}
При возврате обещания:
async function fun(a: string) {
return Promise.resolve(a)
}
Как видите, typescript автоматически выводит для нас определение типа функции, а также автоматически распаковывает возвращенный Promise.
В этом случае мы можем сначала объявить соответствующий тип инструмента:
// PromiseLike 是在 ES6 中进行全局定义的定义文件,可以不用引入,这里只是说明其定义,总的来说就是一类 Promise 的实例
interface PromiseLike<T> {
/**
* Attaches callbacks for the resolution and/or rejection of the Promise.
* @param onfulfilled The callback to execute when the Promise is resolved.
* @param onrejected The callback to execute when the Promise is rejected.
* @returns A Promise for the completion of which ever callback is executed.
*/
then<TResult1 = T, TResult2 = never>(onfulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | undefined | null, onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | undefined | null): PromiseLike<TResult1 | TResult2>;
}
type ResolveValue<T> = T extends PromiseLike<infer V> ? V : T
Вышеупомянутые типы инструментов могут помочь нам проанализировать тип состояния успеха Promise.
Давайте еще раз взглянем на определение типа обычной функции-генератора возвращаемого значения:
function* fun(a: string) {
const b = yield 'b'
const c = yield Promise.resolve('c')
const d = yield Promise.resolve('d')
return a
}
При возврате обещания:
function* fun(a: string) {
const b = yield 'b'
const c = yield Promise.resolve('c')
const d = yield Promise.resolve('d')
return Promise.resolve(a)
}
Видно, что возвращаемый тип функции Generator — это тип Generator, который может принимать три общих параметра, о которых мы можем смутно догадаться по пути:
-
Первый параметр используется каждый раз
yieldТип значения возвращаемого состояния после выражения, этот параметр будет автоматически выведен на основе значения возвращаемого состояния, и все типы будут объединены. -
Второй параметр — это возвращаемое значение функции Generator, но мы можем видеть, что когда мы возвращаем нормальное значение и
fulfilledТип значения отличается, когда Promise состояния отличается, а два типа в async/await действительно одинаковы, и Promise можно распаковать.Я думаю, вы догадываетесь, нам нужно использовать ранее определенный инструмент типResolveValueТипа поправь. -
Мы не можем четко увидеть третий параметр в определении типа на рисунке, поэтому давайте взглянем на определение исходного кода:
interface Generator<T = unknown, TReturn = any, TNext = unknown> extends Iterator<T, TReturn, TNext> { // NOTE: 'next' is defined using a tuple to ensure we report the correct assignability errors in all places. next(...args: [] | [TNext]): IteratorResult<T, TReturn>; return(value: TReturn): IteratorResult<T, TReturn>; throw(e: any): IteratorResult<T, TReturn>; [Symbol.iterator](): Generator<T, TReturn, TNext>; }Вы можете видеть, что третий параметр определяется как
TNext, и используется в двух местах в определении типа, одноnextСреди параметров метода другой используется в унаследованном интерфейсеIteratorв третьем параметре, аIteratorНа самом деле интерфейс выглядит так:interface Iterator<T, TReturn = any, TNext = undefined> { // NOTE: 'next' is defined using a tuple to ensure we report the correct assignability errors in all places. next(...args: [] | [TNext]): IteratorResult<T, TReturn>; return?(value?: TReturn): IteratorResult<T, TReturn>; throw?(e?: any): IteratorResult<T, TReturn>; }Итак, мы видим, что третий параметр на самом деле
nextТип параметра метода, а затем мы также можем узнать из предыдущего изучения функции генератора,nextЗначение, переданное в метод, будет использоваться в качестве возвращаемого значения внутри функции-генератора.Отсюда можно получить
GeneratorТретий параметр интерфейса — это всеyieldОбъединение типов возвращаемых значений.К сожалению, в настоящее время мы не можем использовать этот параметр для подсказки, потому чтоyieldВыражение используется внутри функции Generator, и мы не можем изменить его тип снаружи, если не аннотируем его вручную:function* fun( a: string ): Generator<Promise<string> | 'b', Promise<string>, string> { const b = yield 'b' const c = yield Promise.resolve('c') const d = yield Promise.resolve('d') return Promise.resolve(a) }Видно, что мы все еще можем давать нам подсказки через тип универсального типа, но следует также видеть недостатки этого написания.Это особенно хлопотно.В то же время вам нужно определить три универсальных параметра самостоятельно , а поскольку это комбинированный тип, то подсказка типа на самом деле очень плохая. Лучший способ — определить тип напрямую, когда переменная объявляется внутри функции:
function* fun( a: string ) { const b: string = yield 'b' const c: string = yield Promise.resolve('c') const d: string = yield Promise.resolve('d') return Promise.resolve(a) }Примечание:
- На самом деле автоматический вывод самого третьего родового параметра получен внутренней потребностью
yieldТип переменной возвращаемого значения выводится задом наперед, но, поскольку мы моделируем async/await, мы исходим из определения прямого определения.Вы можете видеть, что значение третьего универсального параметра добавляется автоматически.
- async/await может быть автоматически выведен без какого-либо определения типа
awaitтипа, следующего за выражением, и он будетfulfilledПромис состояния автоматически распаковывается, поэтому типы тоже немного отличаются, в основном потому, чтоyieldа такжеawaitИспользование оригинального дизайна непоследовательно, мы просто используемyieldэтот знак паузы для имитацииawaitФункция единственная, поэтому ее невозможно воспроизвести идеально.
Яма, на которую я наступил
В начале я также думал о создании функции-оболочки, которая может автоматически анализировать входящий тип функции, потому что мы можем видеть, что, поскольку это автоматическая итерация, тип первого параметра должен быть таким же, как тип третьего параметра. Нам нужно только установить эти два одинаковыми, но это очень неразумно, потому что мне нужно автоматически вывести значение универсального параметра интерфейса Generator через саму переданную функцию, а затем напрямую изменить ее общий параметр. значения, которые я в настоящее время не могу сделать с машинописным текстом.
Позже я хочу реализовать его с помощью утверждения типа, чтобы мне нужно было написать только соответствующее определение типа.Немного неприемлемо, что мне нужно заполнять все параметры самому, поэтому я определил еще один.Тип инструмента:
type AsyncFunction<T> = T extends ( ...args: infer A ) => Generator<infer Y, infer R, unknown> ? (...args: A) => Generator<Y, ResolveValue<R>, ResolveValue<Y>> : neverПервоначальная идея заключалась в том, чтобы снова выполнить вывод типа, передав тип самой функции, но когда я закончил ее писать, я обнаружил, что проблема самоссылки самой функции игнорируется. написать идентичную функцию внешне.
funClone, затем используйтеAsyncFunction<typeof funClone>Для выполнения принудительного вывода типов таким способом нагрузка явно будет больше, а также будет сопровождаться проблемой конфликта типов, так что я решительно сдаюсь (плачет). - На самом деле автоматический вывод самого третьего родового параметра получен внутренней потребностью
Что ж, после изучения определения типа Generator нам нужно полагаться на общие параметры, которые он предоставляет, для разработки определения типа для нашей функции-оболочки.
определение типа
Прежде всего, мы должны прояснить, что то, что мы делаем, является функцией-оболочкой, то есть функцией более высокого порядка, поэтому мы должны возвращать функцию, и определение типа этой функции должно быть таким же, как у функции тип, который мы определяем с помощью async/await.
function _asyncToGenerator<R, T = unknown, A extends Array<any> = Array<any>>(
fn: (...args: A) => Generator<T, R, any>
): (...args: A) => Promise<ResolveValue<R>> {
// ...
}
Выше приведено определение типа нашей функции-оболочки, давайте рассмотрим ее шаг за шагом:
-
Использование общих параметров:Если мы хотим написать интерфейс, который автоматически вложил в последующие типы в соответствии с типом входящего значения, общие параметры являются обязательными параметрами, и мы позвольте TypeScript автоматически автоматически использовать нашу сковороду. Параметр типа назначен Также автоматически выводится при использовании в другом месте.
-
Буду
RДженерики (то есть возвращаемое значение функции Генератора) помещаются первыми:из-заTа такжеAиспользуются только как инструмент для вывода типов, иRЕго можно использовать в качестве ручного ввода для управления возвращаемым значением функции Generator.По сравнению с async/await, когда тип возвращаемого значения неясен, мы также можем вручную пометить возвращаемое значение для функции async/await, чтобы контролировать тип возвращаемого значения. . -
Входящий параметр является функцией-генератором:Поскольку нам нужно выполнить вывод типа через входящую функцию, нам нужно использовать общий параметр, чтобы получить здесь тип.
-
Возвращаемое значение
一个返回值为 Promise 的函数:В качестве нашего первоначального требования получение типа входящей функции Generator должно сделать правильный вывод типа для обернутой функции.Тип формального параметра этой функции такой же, как и у функции Generator, а тип возвращаемого значения является результатом распаковки Promise для типа возвращаемого значения функции Generator и оборачивает слой Promise снаружи, просто как асинхронная/ожидающая функция.
логическое письмо
После долгих разговоров я наконец приступил к написанию реального логического кода. Как сказано в начале,Как писать определения машинописного текста также является важной частью этой статьи.Может, это обсессивно-компульсивное расстройство любителей машинописи (смеется). В моем понимании, начиная с определения типа, чтобы составить общее представление о требованиях, можно ускорить последующее развитие бизнеса.Следующее написано с точки зрения определения типа:
function _asyncToGenerator<R, T = unknown, A extends Array<any> = Array<any>>(
fn: (...args: A) => Generator<T, R, ResolveValue<T> | any>
): (...args: A) => Promise<ResolveValue<R>> {
// 需求一:我们要返回一个函数
return function (this: void, ...args) {
// 内部的 this 需要显示定义类型,因为该函数不是构造函数,所以类型为 void 就行了
const self = this
// 需求二:返回函数的函数值需要被 Promise 包裹
return new Promise(function (resolve, reject) {
// 需求三:返回的 Promise 最终的返回值就是 Generator 的返回值,那么这个返回值如何得到呢,我们需要获取到迭代器一直执行下一步,直到遍历到最后 done 的状态第一次为 true
// 获取迭代器实例,将外层的函数作为 this 传入
const gen = fn.apply(self, args)
// 因为我们要完成自动迭代,所以需要对 next 和 throw 方法做一层包装
// 执行下一步
function _next(...nextArgs: [] | [T]) {
// 需求四:使用 yield 模拟 await,同时需要自动迭代,next 方法的返回值的 value 属性是下一次 next 方法的参数
// 我们需要在这里面封装一个自动迭代的函数 asyncGeneratorStep
// asyncGeneratorStep()
}
// 需求五:当遇到 rejected 的 Promise 时在 Generator 内部抛出同步异常
function _throw(err: any) {
// 因为如果捕获到了异常,那么还需要继续往下迭代,所以这里也需要使用自动迭代的函数 asyncGeneratorStep
// asyncGeneratorStep()
}
// 需求六:自动运行迭代器,所以我们需要在函数内部启动迭代器
_next()
})
}
}
Благодаря анализу требований и определению типа мы написали функции, которые обычно должна выполнять самая внешняя функция-оболочка, и следующим шагом является автоматическая итерация внутренней функции.asyncGeneratorStepНаписание кода.
Реализовать итеративную функцию
определение типа
Поскольку итеративная функция используется только внутри, пользователь не может воспринимать ее на внешнем уровне, поэтому мы не будем исследовать здесь, как написать определение типа.Нам нужно только убедиться, что определяемый нами тип может помочь нам в написании логики функцию правильно.
function asyncGeneratorStep<
R,
TNext = unknown,
T extends Generator = Generator
>(
// 生成器(迭代器)实例
gen: T,
// 外层包装 Promise 的 resolve 函数,迭代完毕后最后一次的值就是函数的 return 值
resolve: (value: R) => void,
// 外层包装 Promise 的 reject 函数,使用迭代器的 throw 方法同步抛出异常后如果没有捕获我们需要手动 reject 改变 Promise 状态
reject: (reason?: any) => void,
// 我们上面自己内部封装的 next 和 throw 函数
_next: (...args: [] | [TNext]) => void,
_throw: (err: any) => void,
// 是继续迭代还是抛出错误
key: 'next' | 'throw',
// 只有一个参数,同时需要满足 next 和 throw,所以直接 any 就好了
arg?: any
): void { // 不需要返回值,因为使用的回调函数
// ...
}
логическое письмо
Прежде чем писать код, давайте взглянем на конкретную логику работы:В соответствии с приведенной выше диаграммой и определением типа мы можем быстро написать логический код:
function asyncGeneratorStep<
R,
TNext = unknown,
T extends Generator = Generator
>(
gen: T,
resolve: (value: R) => void,
reject: (reason?: any) => void,
_next: (...args: [] | [TNext]) => void,
_throw: (err: any) => void,
key: 'next' | 'throw',
arg?: any
): void {
// 需要加 try...catch 将 Generator 函数内部未捕获的异常捕获
try {
// yield 表达式后面跟的值,不管 key 是 next 还是 throw 都会返回一样的结构,为了能继续往下面迭代
const { value, done } = gen[key](arg)
if (done) {
// 迭代器完成,直接 resolve 返回值
resolve(value)
} else {
// 将所有值 Promise 化,如果是传入的值是一个 rejected 的 Promise,直接 throw 成同步错误,否则继续往下迭代
Promise.resolve(value).then(_next, _throw)
}
} catch (error) {
// 如果 Generator 函数内部未捕获的异常直接 reject
reject(error)
}
}
Как видите, код основной итерационной функции не сложен, вам просто нужно понять, как каждый этап должен обрабатывать состояние.
весь код
Весь код для рукописного async/await был объяснен, вот весь код:
type ResolveValue<T> = T extends PromiseLike<infer V> ? V : T
function _asyncToGenerator<R, T = unknown, A extends Array<any> = Array<any>>(
fn: (...args: A) => Generator<T, R, any>
): (...args: A) => Promise<ResolveValue<R>> {
return function (this: void, ...args) {
const self = this
return new Promise(function (resolve, reject) {
// 获取实例
const gen = fn.apply(self, args)
// 执行下一步
function _next(...nextArgs: [] | [T]) {
asyncGeneratorStep(
gen,
resolve,
reject,
_next,
_throw,
'next',
...nextArgs
)
}
// 抛出异常
function _throw(err: any) {
asyncGeneratorStep(gen, resolve, reject, _next, _throw, 'throw', err)
}
// 启动迭代器
_next()
})
}
}
function asyncGeneratorStep<
R,
TNext = unknown,
T extends Generator = Generator
>(
gen: T,
resolve: (value: R) => void,
reject: (reason?: any) => void,
_next: (...args: [] | [TNext]) => void,
_throw: (err: any) => void,
key: 'next' | 'throw',
arg?: any
): void {
try {
const { value, done } = gen[key](arg)
if (done) {
resolve(value)
} else {
Promise.resolve(value).then(_next, _throw)
}
} catch (error) {
reject(error)
}
}
есть тест:
const asyncFunc = _asyncToGenerator(function* (param: string) {
try {
yield new Promise<string>((resolve, reject) => {
setTimeout(() => {
reject(param)
}, 1000)
})
} catch (error) {
console.log(error)
}
const a: string = yield 'a'
const d: string = yield 'd'
const b: string = yield Promise.resolve('b')
const c: string = yield Promise.resolve('c')
return [a, b, c, d]
})
asyncFunc('error').then((res) => {
console.log(res)
})
// error
// ['a', 'b', 'c', 'd']
В то же время тип может быть успешно выведен, приятно.
Суммировать
В этой статье машинописный текст используется для реализации async/await с нуля на основе определения типа, в котором основное внимание уделяется определению типа машинописного текста и логике автоматической итерации функции генератора. У автора ограниченные навыки, если есть какие-то ошибки или упущения, пожалуйста, укажите на них в области комментариев и, кстати, попросите 👍.
использованная литература
Различные реализации исходного кода, все, что вам нужно, здесь - реализация async/await
Расширенное программирование с помощью JavaScript (4-е издание)