Подробное руководство по написанию асинхронного кода с обратными вызовами, обещаниями, async await и т. Д.

В документации многих обещаний отсутствуют некоторые важные детали. В этом посте будет попытка осветить эти детали и сделать логику безупречной. Не запоминайте отдельные приложения. Изучите закулисную логику. После этого вы сможете полностью понять концепцию обещаний.

Не расстраивайтесь от количества содержимого. Мне потребовалось много времени, чтобы все это досконально разобраться. Не торопитесь и переваривайте его по крупицам.

Итак, приступим.

Проблема

предположим, что у нас есть два числа x и y, и мы хотим вычислить сумму через три секунды (нереалистичный сценарий, но служит для образовательных целей) и распечатать ее.

Вы можете написать что-то вроде этого:

let x = 5;
let y = 6;
let result;
setTimeout( ()=> {result = x+y; console.log(result)}, 3000)

Мы можем решить это по-другому. Использование обещаний.

Обещания очень полезны для асинхронного программирования. Специально для цепочки обещаний, которая решает проблему «ада обратного вызова» (вложение одна за другой последовательности функций (задач), которые должны выполняться по порядку после завершения предыдущей).

Мы увидим с нуля, что такое обещания, и потренируемся с некоторыми упражнениями.

Часть 1. Основы обещания

Определение, синтаксис, разложение строительных блоков

Обещание - это объект, который обертывает функцию задачи и обрабатывает ее от начала до конца ее выполнения.

Базовый синтаксис:

let examplePromise = new Promise( taskFunction );
examplePromise.then(callbackSuccess, callbackFailure);

Promise.then
Обычно промисы используются вместе с методом then (позже мы также увидим случаи, когда мы не используйте его с «тогда»).

Итак, что такое «taskFunction», «callbackSuccess», «callbackFailure»? Это важные составляющие объявления и выполнения обещания. Это функции. Они должны соответствовать некоторым нормам. Более подробно по порядку:

Задача Функция

taskFunction (с этого момента мы всегда будем называть ее «функцией задачи») представляет собой функцию, подобную следующей:

function taskFunction(resolve,reject) { 
     if ( condition ) {
        resolve(result)
     }  else  {
        reject(new Error("error!"))
     }
}

В качестве входных данных принимает два аргумента. Обе функции, которые мы не определяем. Мы можем просто дать им несколько имен. Обычно разрешить и отклонить (или успех и сбой и т.д…). Это внутренние встроенные функции в Promise, и мы можем только пометить их.

разрешить и отклонить играют важную роль в конвейере выполнения обещания. Они будут обработчиками результата, который будет отправлен в методе then обещания. Не волнуйтесь, мы увидим это в следующем разделе.
На данный момент известно только, что разрешить и отклонить заключить в скобки результат или ошибку, которые мы хотим отправить вперед (смысл которых будет ясен позже).

Если мы получим желаемый результат, мы отправим его через разрешение. Если мы получаем ошибку, мы отправляем ее через отклонение.

Например: предположим, что мы хотим получить некоторые данные. Если задача выполнена успешно, мы сохраняем данные в переменной result и пишем «resolve (result)». Данные, на которые ссылается переменная result, будут отправлены вперед. Если вместо этого получение данных не удастся, мы отправим сообщение об ошибке «отклонить (новая ошибка (« сбой выборки »)». (Изображение скоро станет ясным).

Когда функция taskFunction обнаруживает разрешение или отклонение, она останавливает выполнение, игнорируя оставшиеся строки кода.

Примечание: функция taskFunction не обязательно должна иметь условную конструкцию, как в нашем коде выше. Важно то, что он отправляет некоторые данные, поэтому вы всегда найдете resolve (someResult) в объявлении taskFunction (или reject (что-то)), но вы не найдете только это в практических приложениях, только в учебных примерах, ниже я буду использовать некоторые позже). Часто вы даже не обнаружите отклонения либо потому, что задача не может завершиться неудачей (суммируя два числа, которые мы уже знаем, например: нет способа, которым может произойти ошибка), либо потому, что ошибка может быть передана даже без функции отклонения, но просто вставьте его в тело функции задачи (пока не важно: не волнуйтесь, если вы не поймете это предложение, мы увидим его позже).

callbackSuccess

Функция, обрабатывающая успех (или разрешение / разрешение) функции задачи, «callbackSuccess», будет управлять результатом, который будет передан вниз (дождитесь следующего раздела для всей картины). Таким образом, при его определении он может иметь параметр, который будет принимать значение результата, переданного из Promise.

function callbackSuccess(result) {
   // some operations to be made on "result"
}

callbackSuccess имеет параметр ввода, указывающий на то, какое решение было передано (или, другими словами, что оно заключено в квадратные скобки).
Говоря простыми словами: предположим, что функция taskFunction завершается разрешением: скажем, во встречах, в коде, «resolve (3)». Функция завершает выполнение и отправляет значение «3». В этом случае callbackSuccess присвоит параметру result значение 3: result = 3.

callbackFailure

Это похоже на callbackSuccess. Только то, что параметр input будет ссылаться на то, что reject передал: ошибка. (Примечание: мы используем callbackFailure только в случае ошибок. Он может передавать другие значения, но это не используется в реальных практических приложениях).

Теперь, когда мы обсудили каждый компонент по отдельности (не волнуйтесь, если это еще не имеет смысла, некоторые части будут связаны в следующем разделе), мы можем увидеть, как все они работают вместе.

Основное использование: конвейер выполнения обещания.

Мы видели основное определение обещания и его использование:

let examplePromise = new Promise( taskFunction );
examplePromise.then(callbackSuccess, callbackFailure);

А также как определить функции, участвующие в процессе.
Теперь обратим внимание на код выполнения:

examplePromise.then(callbackSuccess, callbackFailure);

Этот код откроет конвейер выполнения для обещания. (Это всего лишь моя терминология, возможно, вы не найдете ее в другом месте).

Здесь происходит следующее по порядку:

  1. examplePromise выполнит функцию taskFunction, которая обертывает.
  2. taskFunction, в конце своего выполнения в какой-то момент, что происходит при первом столкновении с одной из ключевых функций, разрешающей или отклоняющей - или возникает некоторая ошибка также за пределами reject- (что, как мы напоминаем, закрывает выполнение taskFunction ).
    На этом этапе результат передается через разрешение (или ошибка передается через отклонение). Это будет отправлено как входной аргумент в callbackSuccess (соответственно в callbackError).
  3. Только в этот момент будет выполнен callbackSuccess (в случае разрешения. В случае отклонения callbackFailure), принимая в качестве входного параметра значение, переданное через разрешение (в случае отклонения входом будет ошибка, переданная через отклонение). Обратите внимание, что будет выполняться только одно значение между callbackSuccess и callbackFailure. Никогда и то и другое. Кроме того, обратите внимание, что выполнение «обработчика обратного вызова» (мы указываем этим термином, callbackSuccess в случае успеха или callbackFailure в случае неудачи) начинается только тогда, когда taskFunction завершает свое выполнение.

Пример / упражнение

let examplePromise = new Promise( (res,rej) => { 
      setTimeout( ()=> res(3), 3000)
})
examplePromise.then( (result) => {console.log(result)} )

Это упрощенный пример, в котором мы не используем callbackFailure, потому что задача обязательно завершится успешно (мы можем опустить объявление callbackFailure).

Обратите внимание, что мы написали taskFunction сразу как аргумент конструктора Promise. Мы могли определить это снаружи. Я обычно нахожу это определенным внутри.

Это напечатает значение 3 в консоли через три секунды. В первых трех строках мы определили обещание вместе с функцией задачи.

Затем в последней строке мы запускаем конвейер выполнения обещания.

1. Сначала выполняется taskfunction.
2. CallbackSuccess будет ждать, пока taskFunction не завершится. Это за три секунды. Затем resolve передаст значение 3. Таким образом, результат примет значение 3: result=3.
3. Наконец callbackSuccess выполняется. callbackSuccess - это функция: (result) => {console.log(result)}

Решение исходной проблемы. Первая наивная попытка.

Теперь, увидев этот пример, мы готовы решить с помощью promises начальную проблему, описанную выше (суммирование x и y через 3 секунды).

let x = 5;
let y = 6;
let sumAfterAWhile = new Promise( (res, rej) => {
   setTimeout( ()=>{res(x+y)}, 3000 )
})
sumAfterAWhile.then((result) => {console.log(result)})

Через 3 секунды функция taskFunction отправит в качестве результата сумму x и y, x + y, через функцию разрешения (в приведенном выше коде сокращенно res).

Мы можем написать это еще лучше:

sumAfterAWhile.then((result) => {console.log(result)})
// We can write it as:
sumAfterAWhile.then(console.log) 

Почему? Поскольку console.log уже является функциональным объектом, который принимает в качестве входных данных одну вещь.

Примечание: как указывалось выше, метод then не требует функции callbackFailure. В этом случае по умолчанию установлено значение null.

Проблема все еще существует. Результат мы нигде не храним. Мы будем улучшать код по мере продвижения по руководству, и мы получим инструменты, чтобы справиться с этим.

Часть 2. Некоторые важные факты перед связыванием обещаний

Конвейер выполнения обещания не прерывает основной код.

Допустим, у нас есть следующая ситуация:

let waitAWhile = new Promise((res,rej) => {
   setTimeout( ()=>{res(5)}; 3000)
})
waitAWhile.then( (result) => {console.log(result)} )
console.log(7)

Код читается построчно. Когда выполнение обещания выполнено, начинается выполнение. Но блокирует ли это основное исполнение? Нет. Он открывает новый поток, в котором выполняется обещание, в то время как основной поток продолжает читать и выполнять код построчно. Другими словами, в этом случае он сразу напечатает 7, а через три секунды - 5.

Он не выполняет следующие действия: выполняет обещание, ждет 3 секунды. Отпечатайте 5, а после печати 5 напечатайте 7.

Мы возобновляем это, говоря, что выполнение обещания асинхронно.
Один из способов представить это - думать об основной строке (основном потоке) выполнения, которая течет вперед при чтении и выполнении построчно без остановки. Когда он встречает некоторые асинхронные операции, которые могут занять некоторое время, такие как выполнение обещания (за обещанием следует метод then), основная строка открывает новую ветвь, в которой будет выполняться операция. При этом основная линия не прерывается.

Статус обещаний: разрешенные и отклоненные (и ожидающие рассмотрения). И значение Promises (некоторые называют это результатом).

Мы сказали, что обещание может быть выполнено или отклонено. Это зависит от результата функции taskFunction, которую оборачивает обещание.

Попробуйте написать в консоли вашего браузера следующее:

let examplePromise = new Promise((res,rej) => {
    res(3)
})
examplePromise // same as console.log(examplePromise)

Это напечатает вам Promise {<resolved>: 3}.

Аргумент в скобках ‹› - это статус. И возвращаемое значение - 3.

Следующее,

let examplePromise = new Promise((res,rej) => {
    rej(new Error("issue!!!"))
})
examplePromise // same as console.log(examplePromise)

В результате выдаст Promise {<rejected>: Error : issue!!! …}. Так что статус отклонен. И значение - ошибка.

Также есть статус ожидания, например:

let examplePromise = new Promise((res, rej) => {
    setTimeout(() => { res(3) }, 3000)
})
examplePromise

Это приведет к появлению ожидающего статуса без значения (или, точнее, с неопределенным значением). Статус ожидания носит временный характер. Статус ожидания означает, что функция taskFunction ожидает завершения своего выполнения. Тогда он перейдет в окончательный статус: решено или отклонено.

Почему они важны? Что ж, при работе с обещаниями, в частности, вы заботитесь об их статусе и их значении для следующих выполнений.
status определяет, какой обратный вызов выполнять (если разрешено, то successCallback, если отклонено, то failureCallback, а если ожидает, ничего, пока состояние не изменится на разрешенное или отклоненное).

значение / результат определяет, что передавать в качестве входного аргумента в callbackHandler.

Распространенная ошибка: мы не можем сохранить разрешенный результат с переменной, ссылающейся на обещание.

Допустим, мы хотим сохранить результат того, что обещание вернет через некоторое время. Например, следующее обещание разрешается со значением «hi» через 3 секунды.

let examplePromise = new Promise((res,rej) => {
    setTimeout( ()=> {res("hi")}, 3000) 
})

Если вы хотите сохранить значение «привет», у вас может возникнуть соблазн сделать следующее:

result = examplePromise

Это не сработает. Результатом будет Promise со статусом pending и значением undefined.

Значение, заключенное в функцию разрешения, будет отправлено только в callbackHandlers в методе «then». Это единственный способ продолжить работу со значениями (async / await решит эту проблему. См. Ниже): они рождаются во время выполнения Promise.then и умирают, когда конвейер выполнения заканчивается. Ветвь выполнения обещания рождается и умирает вместе со своими локальными переменными.

Один из способов решения этой проблемы - следующий подход:

let finalResult;
examplePromise.then( (result) => { result = finalResult} )

Инициализация вне переменной, в которой будет храниться результат. Затем позвольте функции callbackSuccess обработать результат: она присоединится к переменной вне finalResult, и результат будет существовать только в конвейере выполнения Promise.then.

Это не очень круто, поскольку мы хотим сохранить как можно меньшее количество переменных, определенных снаружи. Async / await поможет нам с этим справиться.

Увидев это, мы можем попытаться исправить исходную проблему. Мы можем сделать еще одну попытку

Улучшение решения исходной проблемы. 2-я наивная попытка.

У нас остался вопрос для улучшения: как нам сохранить сумму x + y? Как видно из последнего абзаца, это был нетривиальный вопрос. Мы введем внешнюю переменную, в которой будет храниться результат. CallbackSuccess из Promise.then обработает ссылку.

let x = 5;
let y = 6;
let sum;    // Improvement here
let sumAfterAWhile = new Promise( (res, rej) => {
   setTimeout( ()=>{res(x+y)}, 3000 )
})
function callbackSuccess(result) {
     sum = result;    // referencing here
     console.log(result)
}
sumAfterAWhile.then(callbackSuccess) // Execution here

А теперь оставим эту проблему как есть. Закрываем тему обещаний цепочкой обещаний. Мы сделаем последние улучшения этой проблемы после разговора об async / await (после объединения обещаний). Улучшения для этой проблемы не будут использовать цепочку обещаний, поэтому, если вам не терпится увидеть конец этой проблемы, вы можете перейти к async / await. Но не избегайте цепочки обещаний, поскольку это очень важная тема, которую вы должны знать, если хотите создавать веб-сайты.

Часть 3. Цепочка обещаний

Чтобы завершить часть обещаний, давайте погрузимся в цепочку обещаний. Прочитав это, вы лучше поймете, почему вам следует использовать обещания вместо обратных вызовов.

Введение в цепочку

Мотивация. В реалистичном сценарии нам может потребоваться получить некоторые данные. Это может занять некоторое время. После получения данных мы хотели бы выполнить другие операции, такие как извлечение определенной информации из данных, манипулирование ею и т. Д.

Это предпочтительно делать внутри открытого конвейера Promise.then, потому что, как мы видели выше, когда конвейер выполнения закрывается, мы теряем все внутренние переменные внутри конвейера. Если не использовать какие-то внешние переменные. Даже в этом случае мы не могли бы точно знать в основном коде, когда данные были извлечены, так как же операции с данными могут продолжаться вне конвейера?

Тогда кажется естественным, что решение сохраняет конвейер выполнения обещаний открытым и выполняет операции одну за другой в этом потоке выполнения. Итак, у нас есть концепция цепочки обещаний. В качестве интуитивного представления примера кода:

Promise.then(handlers1).then(handlers2)

Легче запомнить как Promise.then.then (а со временем и больше).

Один простой пример: (нереалистичный сценарий, только в целях обучения).
Допустим, мы хотим подождать 3 секунды, а затем суммировать два числа, x и у. Еще через 3 секунды мы хотим умножить результат на 10 и распечатать его.
Код будет (не беспокойтесь, если вы его сейчас не понимаете. К концу этой части все будет ясно):

let x = 1;
let y = 2;
let firstTask = new Promise((res, rej) => {
    setTimeout(() => {res(x+y)}, 3000) //wait 3s and then resolve
})
function secondTask(result) {
    return new Promise((res, rej) => {
        setTimeout(() => {res(10*result)}, 3000)// w. 3s & resolve.
    })
}
firstTask.then(secondTask).then(console.log)

Это напечатает 30 (= 3 * 10) через 6 секунд. Мы связали две операции. Мы проанализируем код после объяснения концепций.

Основная идея цепочки промисов заключается в том, что Promise (taskFunction) будет выполнять taskFunction до тех пор, пока она не будет решена (или отклонена).

Первое после «then» с callbackHandlers (то есть callbackSuccess и, если указано, callbackFailure) обработает результат.

Впоследствии второй следующий за «then» получит что-то от предыдущего then и обработает это с помощью своей функции callbackSuccess (в конечном итоге также callbackFailure). И так далее. Понимание того, как и что получают следующие «затем» в результате - основная цель этой части.

Обещание. Затем обещание

Первый важный факт. Когда у нас есть объект Promise и мы применяем к нему его метод then, будут выполняться не только taskFunction и callbackHandlers. Promise.then возвращает другой объект Promise. Вы можете проверить это в консоли, если не уверены:

let examplePromise = new Promise((res, rej) => {
      res(2)
})
console.log(examplePromise.then((result) => { return result+2 }))
// still a Promise!

Но это должно быть разумным: t тогда метод, в конце концов, это метод, применимый к объектам Promise. Таким образом, имеет смысл, что если мы хотим продолжать применять другое then, имея что-то вроде Promise.then.then, Promise.then должен возвращать Promise (или рассматривать его как само обещание).

С этого момента мы можем ссылаться на объект Promise «Promise.then» как на «then-Promise». Это поможет нам отличаться от основного исходного обещания, которое мы будем называть «основное-обещание». Итак, в приведенном выше примере examplePromise является нашим основным обещанием, а examplePromise.then (...) - нашим тогдашним обещанием.

Чтобы понять, как работает цепочка, достаточно (вы получите этот момент позже) понимания того, что мы получаем как then-Promise (Решено? Это отклонено? С какими значениями?) , из функций main-Promise и обработчиков (callbackSuccess, callbackFailure).

Когда мы выяснили статус (решено, отклонено, ожидает выполнения) и значение тогдашнего обещания, мы выяснили все о тогдашнем обещании. Итак, давайте посмотрим, как мы можем их вывести.

Чтобы понять, что такое then-Promise, нам нужно разобраться в трех разных общих случаях.

1-й случай

CallbackHandler возвращает все, что не является Promise (И в callbackHandler нет ошибки, будь то callbackSuccess или callbackFailure). Впоследствии «then-Promise» будет иметь статус SOLVED, а в качестве VALUE значение, возвращаемое callbackHandler.

Небольшое замечание о терминологии:

Если вы сбиты с толку, я все время использую слово callbackHandler. В callbackHandler я обращаюсь к callbackSuccess, если основное обещание выполнено успешно, и в этом случае будет выполнен callbackSuccess. Я обращаюсь к callbackFailure, если main-Promise отклоняется, и в этом случае будет выполнен callbackFailure. Только один из них будет выполнен.

Предположим, что main-Promise решает задачу taskFunction. Поскольку он успешен, будет выполнен callbackSuccess. Если callbackSuccess возвращает что-либо, что не является Promise (и не вызывает никаких ошибок), тогда then-Promise будет решено (статус решенного) со значением, в точности возвращаемым значением (если функция callbackSuccess ничего не возвращает. Тогда это значение не определено).

Это поведение сохраняется, даже если main-Promise отклоняется (статус отклонен) и callbackFailure, который будет вызван, не вызывает / не вызывает никаких ошибок.

В этом случае значением будет то, что возвращает callbackFailure, если он не выдает никаких ошибок / исключений и статус всегда решается.

Пример 1:

let examplePromise = new Promise((res, rej) => {
      res(2)
})
examplePromise.then(console.log) // the then-Promise

Then-Promise - это разрешенное обещание со значением undefined, поскольку callbackSuccess (в данном случае это функция console.log) ничего не возвращает.

Пример 2: с callbackFailure

let examplePromise = new Promise((res, rej) => {
      rej(2)
})
examplePromise.then(console.log, console.log) // the then-Promise

Обратите внимание, что callbackSuccess и callbackFailure являются функциями console.log.

Прежде всего, в этом случае будет отклонено основное обещание! (rej (2) в коде taskFunction). Итак, callbackFailure будет выполнен. А вот console.log ошибок не выдает. Итак ... Мы попали в условия этого дела.

Какое тогда будет обещание?

Then-Promise - это разрешенное обещание со значением undefined.
Решено, потому что независимо от того, что выполняется (будь то успешный или неудачный обратный вызов), это возвращаемое значение (если есть) функции callbackHandler, которое будет определять статус then-Promise, если в обратных вызовах не возникает ошибок.

Не определено, потому что функция console.log ничего не возвращает.

Небольшое примечание: для тех, кто задается вопросом, почему мы устанавливаем отклонение значения, которое не является ошибкой (2), обычно это не так. В практических приложениях вы всегда отвергаете ошибки.

2-й случай

Если callbackHandler (будь то обратный вызов успеха или неудачи) выдает ошибку во время выполнения, «then-Promise» будет отклонено со значением, сгенерированным ошибкой.

Пример:

let examplePromise = new Promise((res, rej) => {
      res(2)
})
function successCallback(result) {
   throw new Error('throwing an error!')
}
examplePromise.then(successCallback) // the then-Promise

Main-Promise разрешается со значением 2. Следовательно, successCallback будет выполнен (мы даже не удосужились определить failureCallback). Мы видим, что функция successCallback вызывает ошибку (для тех, кто не знает, синтаксис «throw new Error (..)» вызывает ошибку. Это все, что вам нужно знать).
Примечание: мы выкидывая ошибки в successCallback! Мы можем выдавать ошибки где угодно. Было бы то же самое, если бы мы выдавали ошибки в failureCallback.

В этом случае тогда обещание будет отклоненным обещанием и будет оценивать возникшую ошибку. (статус: отклонено; значение: выданная ошибка).

Тот же результат имел бы место, даже если бы функция taskFunction завершилась отклонением, вызвав failCallback, а failCallback выдал ошибку.

Небольшое примечание: есть еще некоторые подробности об ошибках, которые появятся позже. Они не особо важны. Сосредоточьтесь на логике и не теряйтесь в деталях. Ниже я расскажу также о мелких вещах, таких как: поглощение ошибок, уловка и т. Д.

3-й случай: callbackHandler возвращает обещание (и ошибок не возникает). В этом случае тогда-обещание будет в точности возвращенным обещанием.

Пример - именно тот, который мы показали в начале этой части.

let x = 1;
let y = 2;
let mainPromise = new Promise((res, rej) => {
    setTimeout(() => {res(x+y)}, 3000) //wait 3s and then resolve
})
function secondTask(result) {
    return new Promise((res, rej) => {
        setTimeout(() => {res(10*result)}, 3000)// w. 3s & resolve.
    })
}
mainPromise.then(secondTask).then(console.log)

Для начала ответим на несколько вопросов. В качестве упражнения сначала постарайтесь ответить. Тогда прочтите решение.

Q1) Then-Promise, в данном случае это объект Promise «mainPromise.then (secondTask)», решено или отклонено?
Q2) с какое значение?
Q3) Выполняется / отклоняется тогдашнее обещание или сначала ожидается?

Ответы:

Тогдашнее Обещание есть Обещание. Мы находимся в третьем случае, а именно: successCallback (это также могло быть failureCallback), в данном случае функция secondTask возвращает обещание. Следовательно, тогдашнее обещание будет именно возвращенным обещанием. Это будет обработанное обещание (через несколько секунд) со значением 30.

Затем Promise создается через три секунды. Это потому, что главное обещание до сих пор не выполнено. Решится только через 3 секунды со значением 3.

После инициализации then-Promise в течение 3 секунд это обещание будет находиться в состоянии ожидания. По прошествии этих 3 секунд она будет решена со значением 30.
Наконец, второе и последнее «then» (.then (console.log)) отправит значение 30 в качестве входных данных в функцию callbackSuccess, которая является консольной. .log в данном случае.

Возвращаясь к более широкой картине цепочки:

Проблема уменьшилась: вместо main-Promise.then.then мы уменьшили до: then-Promise.then as «Then-Promise = main-Promise.then». И мы прекрасно знаем, как бороться только с одним тогда.

Итак, если у нас есть main-Promise и 4, то мы можем уменьшить его один за другим. Во-первых, понять, что такое «тогдашнее обещание». Итак, как только мы это выясним, у нас будет «тогда-обещание», а затем 3. Затем мы можем продолжить и повторить ту же логику для цепочек.

На этом мы завершаем цепочку.

Когда у вас появляется больше «тогда», вы знаете, как уменьшать их одно за другим, вычисляя время от времени «тогда-обещание». Таким образом, процесс связывания становится циклической процедурой, и вам просто нужно знать одну процедуру, которая сводится к основам Promise, рассмотренным в начале.

Вам следует заниматься спортом. Попробуйте представить себе какое-нибудь упражнение, которое действительно включает в себя процесс выполнения задач. Постарайтесь выяснить, сколько из них вам нужно, и как разбить задачи на отдельные части.

Эти темы непростые, и для их освоения настоятельно рекомендуется практиковаться.

Если хотите, могу поставить в конце список упражнений.

Подведение итогов

Таким образом, конвейер некоторых цепочек промисов перерабатывается в соответствии с одним процессом промисов. Конвейер завершится после последнего обратного вызова в последнем «then».
Отказ от цепочки обещаний: узнайте, как определить статус и значение затем обещание.

Часть 4. Некоторые наблюдения и замечания перед async / await. (Это не так важно, но для полноты картины.)

Здесь мы сделаем несколько небольших наблюдений, которые не обсуждали выше. Это лишь некоторые финальные украшения, которые я не рекомендую смотреть, если вы не усвоили предыдущие концепции.

Мы можем выдать ошибку вместо отправки ошибки через отклонение

Это означает, что это обещание:

let throwing = new Promise((res, rej)=> {
     throw new Error('issue!')
})

И это обещание:

let rejecting = new Promise((res,rej) => {
     rej(new Error('issue!'))
})

Совершенно такие же обещания. Оба имеют одинаковый статус отклоненного и имеют одинаковую ошибку.

Это означает, что если мы присоединим метод then к некоторым обработчикам, оба будут вести себя точно так же: будет выполнен callbackFailure с отправкой той же ошибки.

Таким образом, выброс или отклонение ошибок взаимозаменяемы.
Некоторые люди предпочитают только выдавать ошибки и никогда не использовать функцию отклонения.

Выдача ошибки отличается от возврата ошибки

Это справедливо для цепочки обещаний. Предположим, у нас есть Promise.then.then. И предположим, что первый затем с callbackHandler «вернет (новая ошибка (« проблема! »))». Тогдашнее обещание не будет считаться отклоненным! Следующее не делает «тогдашнее обещание» отклоненным обещанием.

callbackHandler(input) {
     //.... some code...
     //
    return new Error('issue!')
}

Надо кидать ошибки!

callbackHandler(input) {
     //.... some code...
     // if failed: 
     throw new Error('type of error...')

     // eventually other code
}

Улавливающие и поглощающие ошибки в цепочке.

Вы, наверное, слышали о методе catch для обещания. Он принимает в качестве входных данных только функцию: callbackFailure в случае отклонения.

catch(callbackFailure) {...}

Метод catch (f) эквивалентен методу then (null, f).

Обычно распространены следующие практики: метод then часто используется только с callbackSuccess, опуская callbackFailure. А для управления ошибками / отклонениями мы используем уловку. Так, например, у нас может быть цепочка следующего типа:

Promise.then(cS1).then(cS2).catch(cF1).then(cS3).catch(cF2)

Где cS означает callbackSuccess, а cF - callbackFailure.

Когда ошибка «перехватывается» посредством callbackFailure, она исчезает из конвейера выполнения цепочки обещаний. Более того, если возникает ошибка, она отправляется вниз до тех пор, пока не будет обнаружен перехват (если есть) и обрабатывает ошибку.

В приведенном выше примере, если ошибка возникает в cS1, впоследствии «.then (cS2)» не обработает ее и отправит ее в «.catch (cF1)», который поглотит ошибку.

Рекомендуется всегда ставить защелку в конце цепи.

Promise.then(cS1).then(cS2)

Эта цепочка не обрабатывает никаких ошибок. Если ошибка возникает в Promise, впоследствии эта ошибка будет отправлена ​​в «then-Promise» и в «then-then-Promise», поскольку она никогда не обрабатывается.

Часть 5. Async / Await

Ранее мы ожидали, что данные, обрабатываемые внутри конвейера выполнения цепочки промисов, просто живут и умирают там, не имея возможности ссылаться на них напрямую.

Async / Await решает эту проблему.

Синтаксис

Async ставится перед объявлением функции:

async function saveResult(input) {
    let waitPromise = new Promise((res, rej) => {
         setTimeout(() => resolve(input), 5000)
    });
    let result = await waitPromise; 
    console.log(result);
}
saveResult('hi') // Will print hi after 5 seconds.

Await используется внутри функции с объявлением async и ставится перед обещанием.

Await позволяет остановить выполнение функции до тех пор, пока обещание не будет разрешено или отклонено. Это означает, что любая другая строка ниже await (я имею в виду внутренний код внутри функции async) не будет прочитана до тех пор, пока Promise не будет решен, после чего будет возвращено значение разрешения (это не останавливает выполнение функции) и сохраняется в переменной результата.

В приведенном выше примере переменная result будет разрешенным значением только после того, как Promise будет разрешен. Этим способом:

Мы захватываем окончательные данные конвейера Promise.
Наконец, обратите внимание, что нам не нужен метод Promise. У нас может быть цепочка обещаний (что, как мы видели, является «тогда-то-… -обещание»). В этом случае await возвращает окончательное значение этого длинного конвейера.

Наконец, функции, объявленные с помощью async, являются асинхронными по отношению к внешнему коду.

saveResult('hi')
console.log(2)

Первая строка выполняется и открывает новый поток (функция async, определенная выше). Основной поток не останавливается: читается строка сразу после.

Таким образом, сразу печатается значение 2. И через 5 секунд печатается значение «hi», поскольку выполнение асинхронной функции «saveResult» происходит в другой ветви выполнения.

Наконец, давайте исправим нашу Первоначальную проблему

Теперь, когда мы увидели async / await, код можно улучшить:

let x = 5;
let y = 6;
let sumAfterAWhile = new Promise( (res, rej) => {
   setTimeout( ()=>{res(x+y)}, 3000 )
})
async function success() {
     let sum = await sumAfterAWhile;  
     console.log(sum);
     return sum
}
success() // prints and returns the sum after 3 seconds.

Другие улучшения:

Наконец, мы можем захотеть иметь x и y в качестве свободных параметров. Мы можем поместить x и y в качестве параметров в асинхронную функцию и вложить Promise:

async function success(x,y) {
     let sumAfterAWhile = new Promise( (res, rej) => {
         setTimeout( ()=>{res(x+y)}, 3000 )
     })     
     let sum = await sumAfterAWhile;  
     console.log(sum);
     return sum
}

Последнее замечание: без использования async / await мы могли бы использовать общий прием - Оборачивание обещания в функции. Вместо Promise.then мы могли бы иметь «specificFunction.then», где specificFunction - это функция, возвращающая обещание. Итак, если бы мы хотели установить x и y в качестве входных аргументов конкретной функции, у нас было бы что-то вроде:

function particolarFunction(x,y) {
     return new Promise((res,rej) => {
           setTimeout( ()=>{res(x+y)}; 3000) 
     })
}
particularFunction(5,6).then(console.log) 
// IF we named the particularFunction as sumAfterAWhile, 
// The code becomes immediately explicit and this is desirable.

Тем не менее, существует проблема сохранения временных данных из конвейера. Вот почему предпочтительнее async / await.

Вывод

Если вы прочитали весь этот пост (и, надеюсь, поняли), поздравляем! Надеюсь, это было полезно. Это было тяжело и долго. Но теперь вы должны почувствовать себя мастером обещаний!

До следующего раза, хорошего дня!

PS. В этом длинном посте могут быть ошибки. Любые исправления приветствуются.