
Если вас заинтересовала эта статья, то вы, наверное, несколько разбираетесь в асинхронном программировании на JavaScript и, возможно, интересуетесь, как оно работает в TypeScript.
Поскольку TypeScript – это надмножество JavaScript, async/await там работает точно так же, но с некоторыми дополнительными бонусами и безопасностью типов. TypeScript позволяет запрограммировать безопасность типа ожидаемого результата и даже проверить, нет ли ошибок, связанных с типом. Поэтому баги отлавливаются на ранних стадиях разработки программы.
В сущности, async/await – это синтаксический сахар для промисов, то есть, ключевое слово async/await обертывает промисы. Функция async всегда возвращает промис. Даже если пропустить ключевое слово Promise, компилятор обернет вашу функцию в немедленно разрешаемый промис.
Давайте покажу:
const myAsynFunction = async (url: string): Promise<T> => {
    const { data } = await fetch(url)
    return data
}
const immediatelyResolvedPromise = (url: string) => {
    const resultPromise = new Promise((resolve, reject) => {
        resolve(fetch(url))
    })
    return  resultPromise
}
Пусть они и выглядят совершенно по-разному, два вышеприведенных фрагмента кода более-менее эквивалентны. Async/await просто позволяет писать код в более синхронной манере и избавляет от необходимости встраивать промис в строку. Это очень мощный прием, если имеешь дело со сложными асинхронными паттернами.
Чтобы выжать максимум из синтаксиса async/await, нужно иметь базовое представление о промисах. Давайте подробнее рассмотрим, что представляют собой промисы на фундаментальном уровне.
Что такое промис в TypeScript?
В переводе с английского «promise» означает «обещание». В JavaScript промис описывает ожидание того, что некоторое событие произойдет в определенный момент, и ваше приложение полагается на результат этого будущего события при выполнении определенных других задач.
Чтобы показать, что я имею в виду, разберу реалистичный пример, выражу его в псевдокоде, а затем в действующем коде TypeScript.
Допустим, мне нужно покосить газон. Звоню в газонокосильную компанию, где мне обещают, что через пару часов придет человек и покосит газон. Я, в свою очередь, обещаю сразу же ему за это заплатить, при условии, что мой газон будет выкошен как следует.
Заметили паттерн? Первая очевидная вещь, которую нужно отметить – второе событие полностью полагается на первое. Если будет выполнено обещание, заложенное в первом событии, то выполнится и следующее событие. Промис в том событии либо выполняется, либо не выполняется, либо остается в подвешенном состоянии.
Рассмотрим эту последовательность шаг за шагом и выразим ее в коде.

Синтаксис промиса
Прежде, чем написать весь код, давайте разберемся в синтаксисе промиса – конкретно, такого промиса, который разрешается в строку.
Мы объявили promise при помощи ключевого слова new + Promise, где промис принимает аргументы resolve и reject. Теперь давайте напишем промис, выражающий события из вышеприведенной блок-схемы.
// Я отправляю запрос в компанию. Он синхронный
// Компания обещает мне выполнить работу
const angelMowersPromise = new Promise<string>((resolve, reject) => {
    // Обещание разрешилось спустя несколько часов
    setTimeout(() => {
        resolve('We finished mowing the lawn')
    }, 100000) // разрешается спустя 100 000 мс
    reject("We couldn't mow the lawn")
})
const myPaymentPromise = new Promise<Record<string, number | string>>((resolve, reject) => {
    // разрешившийся промис с объектом: платежом в 1000 евро
    // и большое спасибо
    setTimeout(() => {
        resolve({
            amount: 1000,
            note: 'Thank You',
        })
    }, 100000)
    // промис отклонен. 0 евро и отзыв «неудовлетворительно» 
    reject({
        amount: 0,
        note: 'Sorry Lawn was not properly Mowed',
    })
})
В вышеприведенном коде объявлены как обещания компании, так и наши обещания. Обещание компании либо выполняется через 100 000 мс, либо отклоняется. Promise всегда находится в одном из трех состояний: resolved, если ошибки нет, rejected, если встретилась ошибка, или pending, если обещание promise пока ни отклонено, ни выполнено. В нашем случае все это укладывается в период 100000ms.
Но как нам выполнить эту задачу последовательным синхронным образом? Здесь-то и пригодится ключевое слово then. Без него функции просто выполняются в том же порядке, в котором и разрешаются.
Последовательное выполнение с .then
Теперь можно сцепить промисы, что позволяет выполнять их последовательно с применением .then. Эти функции похожи на обычный человеческий язык: сделай так, а затем вот это, а потом то и так далее.
angelMowersPromise
    .then(() => myPaymentPromise.then(res => console.log(res)))
    .catch(error => console.log(error))
Вышеприведенный код выполнит angelMowersPromise. Если с этим ошибки не случится, он выполнит myPaymentPromise. Если в одном из двух промисов возникнет ошибка, то она будет отловлена в блоке catch.
Теперь давайте рассмотрим более технический пример. При программировании клиентского интерфейса есть типичная задача: выполнять запросы по сети и адекватно реагировать на их результаты.
Ниже – запрос, требующий выбрать список сотрудников с удаленного сервера.
const api =  'http://dummy.restapiexample.com/api/v1/employees'
   fetch(api)
    .then(response => response.json())
    .then(employees => employees.forEach(employee => console.log(employee.id)) // логирует id всех сотрудников
    .catch(error => console.log(error.message))) // логирует любую ошибку, приходящую от промиса
Бывает так, что необходимо параллельно или последовательно выполнять сразу множество обещаний. В подобных сценариях особенно полезны такие конструкции как Promise.all или Promise.race.
Представьте, к примеру, сто нужно выбрать список из 1 000 пользователей GitHub, а затем сделать дополнительный запрос с ID, чтобы выбрать для каждого из них аватарки. Совсем не обязательно вы захотите дожидаться завершения этих операций со всеми пользователями в последовательности; вам нужны только все выбранные аватарки. Мы подробнее поговорим об этом ниже, когда будем обсуждать Promise.all.
Теперь, когда вы в общем и целом поняли, что такое промисы, давайте рассмотрим синтаксис async/await.
async/await
Синтаксис Async/await удивительно прост при работе с промисами. Он предоставляет простой интерфейс для чтения и записи промисов, причем, таким образом, что они кажутся синхронными.
Конструкция async/await всегда возвращает Promise. Даже если пропустить ключевое слово Promise, компилятор обернет вашу функцию в немедленно разрешаемый промис. Таким образом, можно трактовать возвращаемое значение функции async как Promise, что довольно полезно, когда нужно разрешать сразу множество асинхронных функций.
Как понятно из названия, async с await всегда ходят парой. То есть, делать await можно только внутри функции async. Функция async сообщает компилятору, что это асинхронная функция.
Если преобразовать вышеприведенные промисы, то получится такой синтаксис:
const myAsync = async (): Promise<Record<string, number | string>> => {
    await angelMowersPromise
    const response = await myPaymentPromise
    return response
}Сразу заметно, что этот код выглядит более удобочитаемым и кажется синхронным. В строке 3 мы сказали компилятору дожидаться выполнения angelMowersPromise, и только потом делать что-то еще. Затем возвращаем отклик от myPaymentPromise.
Возможно, вы заметили, что здесь мы пропустили обработку ошибок. Это можно было бы сделать в блоке catch после .then в промисе. Но что делать, если нам попадется ошибка? Это приводит нас к блоку try/catch.
Обработка ошибок в try/catch
Вернемся к примеру с выбором записей о сотрудниках, чтобы показать обработку ошибок в действии, поскольку именно при выполнении запроса по сети ошибка вполне может возникнуть.
Допустим, например, что у нас лег сервер, либо что мы отправили запрос в неверном формате. Мы должны приостановить выполнение, чтобы предотвратить обвал программы. Синтаксис будет выглядеть так:
interface Employee {
    id: number
    employee_name: string
    employee_salary: number
    employee_age: number
    profile_image: string
}
const fetchEmployees = async (): Promise<Array<Employee> | string> => {
    const api = 'http://dummy.restapiexample.com/api/v1/employees'
    try {
        const response = await fetch(api)
        const { data } = await response.json()
        return data
    } catch (error) {
        if (error) {
            return error.message
        }
    }
}
Мы инициировали функцию async. В качестве возвращаемого значения ожидаем массив типа typeof с информацией о сотрудниках, либо строку с сообщениями об ошибке. Соответственно, тип Promise формулируется как Promise<Array<Employee> | string>.
В блоке try находятся выражения, которые функция должна выполнять, если ошибок не будет. Блок catch захватывает любую возникающую ошибку. В таком случае мы просто возвращаем свойство message объекта error.
Красота происходящего заключается в том, что любая ошибка, рождающаяся в блоке try, сразу выбрасывается и захватывается блоком catch. Если какое-то исключение ускользнет, то может получиться код, плохо поддающийся отладке, либо даже может быть испорчена вся программа.
Конкурентное выполнение при помощи Promise.all
Как я говорил ранее, бывает, что обещания должны выполняться параллельно.
Продолжим пример с нашим API для выбора сотрудников. Допустим, нам нужно выбрать всех сотрудников, затем выбрать их имена, затем сгенерировать на основе имен электронные сообщения. Очевидно, нам нужно выполнять эти функции в синхронной манере, но при этом параллельно, чтобы одна функция не блокировала другую.
В данном случае мы воспользуемся Promise.all. Как пишет Mozilla, “Promise.all обычно применяется после того, как было запущено множество асинхронных задач, которые должны работать конкурентно, и после того, как пообещали, каковы будут их результаты – чтобы можно было дождаться, пока все эти задачи будут завершены.”
В псевдокоде было бы что-то подобное:
- Выбрать всех пользователей => - /employee
- Дождаться всех данных о пользователях. Извлечь - idот каждого пользователя. Выбрать каждого пользователя =>- /employee/{id}
- Сгенерировать электронное сообщение для каждого пользователя по его имени 
const baseApi = 'https://reqres.in/api/users?page=1'
const userApi = 'https://reqres.in/api/user'
const fetchAllEmployees = async (url: string): Promise<Employee[]> => {
    const response = await fetch(url)
    const { data } = await response.json()
    return data
}
const fetchEmployee = async (url: string, id: number): Promise<Record<string, string>> => {
    const response = await fetch(`${url}/${id}`)
    const { data } = await response.json()
    return data
}
const generateEmail = (name: string): string => {
    return `${name.split(' ').join('.')}@company.com`
}
const runAsyncFunctions = async () => {
    try {
        const employees = await fetchAllEmployees(baseApi)
        Promise.all(
            employees.map(async user => {
                const userName = await fetchEmployee(userApi, user.id)
                const emails = generateEmail(userName.name)
                return emails
            })
        )
    } catch (error) {
        console.log(error)
    }
}
runAsyncFunctions()В вышеприведенном коде fetchEmployees выбирает всех сотрудников из  baseApi. Мы ожидаем отклик (await), преобразуем его в JSON, а затем возвращаем преобразованные данные.
Самое важное, о чем здесь нужно помнить – как мы последовательно выполняли код строка за строкой внутри функции async с ключевым словом await. Мы бы получили ошибку, если бы попытались преобразовать в JSON данные, которых дождались не полностью. То же касается fetchEmployee, с той оговоркой, что выбирали бы всего одного сотрудника. Более интересен фрагмент runAsyncFunctions, где все асинхронные функции выполняются конкурентно.
Сначала обернем в блок try/catch все методы, находящиеся внутри runAsyncFunctions. Далее ждем (await) результат выбора всех сотрудников. Нам нужен id каждого сотрудника, чтобы выбрать соответствующие им данные, но в конечном счете нам нужна именно информация о сотрудниках. 
Вот где можно прибегнуть к Promise.all, чтобы конкурентно обработать все Promises. Каждый fetchEmployee Promise конкурентно выполняется для всех сотрудников. Информация о сотрудниках, которую мы дождемся, используется для генерации электронного сообщения от каждого сотрудника, это делается при помощи функции generateEmail.
Если случится ошибка, то она распространяется как обычно, от невыполненного обещания к Promise.all, а затем превращается в исключение, которое можно отловить в блоке catch.
Ключевые выводы
async и await позволяет писать асинхронный код так, что он выглядит и действует как синхронный. Такой код становится гораздо проще читать, писать и судить о нем.
Завершу статью несколькими ключевыми тезисами; помните о них, когда будете работать над вашим следующим асинхронным проектом на TypeScript.
- awaitработает только внутри функции- async
- Функция, помеченная ключевым словом - async, всегда возвращает- Promise
- Если возвращаемое значение внутри - asyncне возвращает- Promise, то оно будет обернуто в немедленно разрешаемый- Promise
- Как только встретится ключевое слово - await, выполнение приостанавливается, пока не будет завершено- Promise
- awaitлибо вернет результат от выполненного- Promise, либо выбросит исключение от отклоненного- Promise
Комментарии (18)
 - Metotron025.10.2021 14:44+1- Вопрос по языку: зачем после Promise написали обобщённый тип <T>, если он больше нигде не упоминается? Я думал, это нужно, чиобы обрзначить, что на выходе или у какого-то параметра такой же тип, как на входе, но здесь ни один параметр не имеет тип T и на выходе его нет.  - Xazzzi25.10.2021 15:11- Ошибка же, там дожен был быть - unknownили- anyна худой конец. - Metotron026.10.2021 01:58- А, это какой-то обёрнутый в скрытую логику тип данных TS, которому нужно передать, что будет после разрешения промиса?  - Xazzzi26.10.2021 11:05+1- Вы правильно пишете, тип - Тнеоткуда взять. Вместо- Promise<T>для результата можно прописать- Promise<unknown>, тогда значение которое выдаст await будет иметь тип- unknownи не получится его случайно использовать не проверив на соответствие типу (например через type predicate), в отличии от- any.
 
 
 
 - faiwer25.10.2021 17:24+10- const runAsyncFunctions = async () => { try { const employees = await fetchAllEmployees(baseApi) Promise.all( employees.map(async user => { const userName = await fetchEmployee(userApi, user.id) const emails = generateEmail(userName.name) return emails }) ) } catch (error) { console.log(error) } }- вы тут - awaitперед- Promise.allзабыли. Без него всё насмарку.- upd1. и ещё забыли - return, иначе какой смысл писать- return emails.
 upd2.- и скорее всего нуженпеременная названа криво, т.к. email один, должно быть- .flat()а то у вас странный список- email[][]получается :-)- email(без- s).
 - Delagen26.10.2021 11:05- Первый же пример "более менее" не эквивалентен. Первый возвращает свойство data от результата fetch, а второй просто результат. 
 - qrKot26.10.2021 21:45-1- Ну ребят, ну чесслово, издательский дом... Вы этот текст сюда принесли для того, чтобы на корректорах сэкономить? - В переводе с английского «promise» означает «обещание». В JavaScript промис описывает ожидание того, что некоторое событие произойдет в определенный момент, и ваше приложение полагается на результат этого будущего события при выполнении определенных других задач. - ... - Заметили паттерн? Первая очевидная вещь, которую нужно отметить – второе событие полностью полагается на первое. Если будет выполнено обещание, заложенное в первом событии, то выполнится и следующее событие. Промис в том событии либо выполняется, либо не выполняется, либо остается в подвешенном состоянии. - ... - Мы объявили - promiseпри помощи ключевого слова- new + Promise, где промис принимает аргументы- resolveи- reject. Теперь давайте напишем промис, выражающий события из вышеприведенной блок-схемы.- ... - Теперь можно сцепить промисы, что позволяет выполнять их последовательно с применением - .then. Эти функции похожи на обычный человеческий язык: сделай так, а затем вот это, а потом то и так далее.- Шел 16-й (шестнадцатый) абзац пространной статьи об асинхронном программировании в TS, но до сих пор ни разу не было упомянуто, чем все эти городки с Promise'ами отличаются от if/then/else. - Мы рассказали про порядок выполнения промисов, про то, как их chain'ить, трижды привели косячные примеры кода, определили зависимость выполнения одних промисов от результатов других... - Бывает так, что необходимо параллельно или последовательно выполнять сразу множество обещаний. - 20-й абзац... уже где-то почти совсем рядом... - Представьте, к примеру, сто нужно выбрать список из 1 000 пользователей GitHub, а затем сделать дополнительный запрос с ID, чтобы выбрать для каждого из них аватарки. Совсем не обязательно вы захотите дожидаться завершения этих операций со всеми пользователями в последовательности; вам нужны только все выбранные аватарки. Мы подробнее поговорим об этом ниже, когда будем обсуждать - Promise.all.- Нет, блин, опять какую-то фигню написали. Каким боком Promise.all помогает "не обязательно ... дожидаться завершения этих операций со всеми пользователями в последовательности" - решительно непонятно. Он же, вроде, и нужен для того, чтобы всенепременнейше дождаться прямо конкретно всех, без исключения? - Теперь, когда вы в общем и целом поняли, что такое промисы - Нет, все еще решительно не понимаю... Так ни разу слов "асинхронное выполнение" и примеров, хотя бы как-то с оными связанных, и не встретил. А так-то 25-й абзац уже... - Синтаксис Async/await удивительно прост при работе с промисами. Он предоставляет простой интерфейс для чтения и записи промисов, причем, таким образом, что они кажутся синхронными - Ребят, вы весь текст до этого ни разу не сказали, что промисы - асинхронные! Ну камон, вы чо, блин? - Даже если пропустить ключевое слово - Promise, компилятор обернет вашу функцию в немедленно разрешаемый промис.- Ну вот может у меня компилятор какой-то не такой, но конкретно мой компилятор ничего ни во что оборачивать не стал. Он просто вывалил мне ошибку и ничего собирать не стал. И я даже с ним согласен, я бы тоже не стал. - Конструкция - async/awaitвсегда возвращает- Promise- Ну нет же, право слово. async-функция всегда возвращает Promise, а await всегда дожидается, пока он зарезолвится. А что такое конструкция async/await - решительно непонятно. Например, можно объявить async функцию, и не await'ить ее. А await вполне себе используется не только в вызовах async-функций, например, можно `await somePromise` делать, это законно. И это именно потому, что никакой конструкции async/await нет, есть async(асинхронные функции), которые всегда обязаны возвращать промисы, и есть ключевое слово await, которое заставляет текущий поток исполнения дождаться, пока уже возвращенный асинхронной функцией промис зарезолвится. - Таким образом, можно трактовать возвращаемое значение функции async как - Promise- Зачем трактовать-то? Это ж он и есть! - что довольно полезно, когда нужно разрешать сразу множество асинхронных функций - Опачки, здравое зерно! Какой по счету абзац? Я что-то сбился. - Как понятно из названия, - asyncс- awaitвсегда ходят парой.- Блин, вы опять все испортили! Во-первых, вообще не понятно из названия, во-вторых, вообще не обязаны парой ходить... Можно пнуть асинхронную функцию и не дожидаться выполнения - это законно, хоть и не приветствуется (async без await). Можно просто взять готовый промис и дождаться его выполнения, что не только законно, но и в целом очень даже приветствуется (await без async). - То есть, делать - awaitможно только внутри функции- async- Ну да, await может быть использован только внутри async-функции, это да. Но ваше "то есть" предполагает, что это само собой разумеется из сказанного ранее. А это не так... Вообще никакой связи. - Тем более что запрет на использование await'а извне асинхронных функций таки стоит сильно сбоку от "async/await, ходящих парой". Если мы уж пару ищем, то, предположительно, имеем в виду связанную между собой пару из вполне конкретного async в сигнатуре функции и конкретного await'а, который резолва конкретно возвращаемого ей промиса ждет. Ну так вот, сейчас будет срыв покровов: тот await, который ждет разрешения асинхронной функции всегда и гарантировано находится снаружи относительно самой функции. - Вы что, просто из MSDN в произвольном порядке фразы дергаете и гуглотранслейтом их переводите? - const myAsync = async (): Promise<Record<string, number | string>> => { await angelMowersPromise const response = await myPaymentPromise return response }- Сразу заметно, что этот код выглядит более удобочитаемым и кажется синхронным. - Да он в контексте описанной функции и является синхронным! Ключевое слово async = асинхронное выполнение, а ключевое слово await - это оператор синронизации состояния. - Допустим, например, что у нас лег сервер - Мы должны приостановить выполнение, чтобы предотвратить обвал программы - Что, простите, мы должны предотвратить? Вы же сами говорите, он УЖЕ лег. - В качестве возвращаемого значения ожидаем массив типа - typeof- Странный какой-то тип. У меня, почему-то, есть чувство, что тип с именем typeof в TypeScript объявить нельзя... - Если какое-то исключение ускользнет, то может получиться код, плохо поддающийся отладке, либо даже может быть испорчена вся программа. - Ну, например, ровно как в вашем примере, выброшенный Exception (который не является наследником Error) вполне "испортит всю программу". - Очевидно, нам нужно выполнять эти функции в синхронной манере, но при этом параллельно, чтобы одна функция не блокировала другую. - Вот сейчас вообще не очевидно стало. Вы уж определитесь, синхронно (выполнение инструкций последовательно, в порядке описания в коде программы), или таки параллельно (очевидно, в каком-то другом порядке). Да и вообще, откуда вы это "параллельно" взяли. Асинхронно - правильный термин, параллельность TS вообще никак не гарантирует. - Или про то, чем асинхронность от параллельности отличается, тоже надо рассказать? - Как пишет Mozilla, “ - Promise.allобычно применяется после того, как было запущено множество асинхронных задач, которые должны работать конкурентно, и после того, как пообещали, каковы будут их результаты – чтобы можно было дождаться, пока все эти задачи будут завершены.”- Зачем вы оговариваете Mozilla? Не могли они такую дичь написать! Это просто у вас перевод корявый. - В псевдокоде было бы что-то подобное: - Выбрать всех пользователей => - /employee
- Дождаться всех данных о пользователях. Извлечь - idот каждого пользователя. Выбрать каждого пользователя =>- /employee/{id}
- Сгенерировать электронное сообщение для каждого пользователя по его имени 
 - Псевдокод у вас тоже неправильный, последовательный (т.е. синхронный) алгоритм описан. При чем тут Promise.all? - Мы ожидаем отклик ( - await), преобразуем его в- JSON- А вы это, простите, зачем делаете? (подсказка: в коде происходит не то, что вы пишете). - Самое важное, о чем здесь нужно помнить – как мы последовательно выполняли код строка за строкой внутри функции - asyncс ключевым словом- await. Мы бы получили ошибку, если бы попытались преобразовать в JSON данные, которых дождались не полностью.- Божечки, что вы несете? Муля, никогда не доверяй важное дело идиотам! - Более интересен фрагмент - runAsyncFunctions, где все асинхронные функции выполняются конкурентно.- Вот это верно, он, как раз, и есть то, заради чего вся эта муть писалась. Это прям апогей, апофеоз, самая цели статьи и вишенка на торте... - Каждый - fetchEmployee Promiseконкурентно выполняется для всех сотрудников.- ... которая в тупую и совершенно бездарно слита буквально через абзац. Как и вся статья... - asyncи- awaitпозволяет писать асинхронный код так, что он выглядит и действует как синхронный- Просто нет слов... Еще раз повторю: async - ключевое слово, означающее асинхронный запуск, await - инструмент синхронизации. await не позволяет писать асинхронный код, await - напрямую команда синхронного исполнения. - Честное слово: - МуляИздательский дом "Питер", не доверяй- серьезное делопубликацию статей от своего имени на хабре- идиотамрерайтерам. - Saiv4603.11.2021 07:39+2- Человек по делу написал, а вы его минусуете. - Конечно и оригинал такой себе, вот пример: - // Я отправляю запрос в компанию. Он синхронный // Компания обещает мне выполнить работу const angelMowersPromise = new Promise<string>((resolve, reject) => { // Обещание разрешилось спустя несколько часов setTimeout(() => { resolve('We finished mowing the lawn') }, 100000) // разрешается спустя 100 000 мс reject("We couldn't mow the lawn") })- Данный Promise будет отвергнут сразу же, не смотря на то, что спустя 100с он будет разрешён (да и писал это человек что явно не знает как работают промисы). 
 
 - Julia_Konovalova28.10.2021 12:48+1- Спасибо! Было полезно прочитать ещё раз другими словами и с понятными примерами, хоть мне пока не нужен Typescript, я изучаю JS - В части Promise.All было уже не так легко понять, но всё равно спасибо ???? 
 - muturgan29.10.2021 15:27- Довольно подробное руководство по промисам и async/await. Но при чем тут тайпскрипт? 
 
           
 
MaZaAa
Режет глаза отсутствие; в конце строк в коде. Не надо свои странные предпочтения выставлять на публику.
haymob
Typescript дописывает ; при компиляции в js, если вам не доплачиваю за ; результат будет одинаковый.
mukhindev
С каких пор это порицается? Пунктуальная нетерпимость какая-то) На всекий случай предостерегаю вас от захода в некоторые открытые репозитории и документации этих компаний https://standardjs.com/#who-uses-javascript-standard-style
qrKot
Странно видеть ссылку на JS-стайл в контексте статьи по TS...
Тем более линтер может нарисовать эти ; за вас, а публиковать в статье код, не видевший линтера, таки моветон.
muturgan
Знаю проекты на тс, в которых в стайлгайдах и настройках линтера закреплено отсутствие точек с запятой.
Приравнивать несоответствие вашим предпочтениям к недостатку, таки моветон.
ПС. Я люблю ;
Metotron0
Разве этот комментарий не является эттм самым выставлением?