Все началось с асинхронного программирования. Как справедливо сказал Платон, Необходимость - мать всех Изобретений, чтобы справиться с непредсказуемой природой асинхронных программ, было рождено Обещание! (Гром, молния и большие приливные волны на заднем плане. :P)
Простите мое драматическое вступление. Я хотел сделать обещания интересными.
Чтобы лучше понять обещания, нам нужно иметь представление об асинхронном программировании. Я не слишком углублялся в эту концепцию, но сделал слабую попытку дать вам лишь беглый взгляд на нее.
Что такое асинхронное программирование?
Асинхронное программирование — это способ, который позволяет вашей программе запускать потенциально длительную задачу и по-прежнему реагировать на другие события во время выполнения этой задачи, а не ждать, пока эта задача завершится. законченный. Как только эта задача будет завершена, ваша программа будет представлена с результатом.
function delay(time) { setTimeout(executeAfterDelay, time); } function executeAfterDelay() { console.log("Print after delay"); } function asyncCall() { console.log("Calling delay function"); delay(2000); console.log("After delay function"); } asyncCall(); //Console log Results: //Calling delay function //After delay function //Print after delay (after a delay of 2 seconds)
Когда вызывается функция asyncCall, выполняется первая строка внутри нее, затем вызывается функция задержки, но Javascript не ждет, пока она не будет полностью выполнена, переходит к следующей строке, затем, когда функция задержки выполняется, функция обратного вызова, связанная с тайм-аут выполняется.
Следующий очевидный вопрос: что такое обратные вызовы?
обратные вызовы могут показаться сложными, но они не что иное, как функции, которые передаются в качестве аргументов другим функциям.
Пример 1:
Пример 2:
Но проблема с обратными вызовами заключается в том, что они никогда не могут возвращать значения или выдавать ошибки.
Существует также проблема попадания в ад обратных вызовов или спагетти-код.
Теперь представьте, если бы мне пришлось вызывать несколько вызовов API, где я должен ждать ответа на предыдущий вызов API, чтобы вызвать следующий вызов.
Для этого мне пришлось бы написать вложенные обратные вызовы.
Это будет выглядеть примерно так.
//First API Request $.ajax({ url:'endpoint/v1/request1', type: "GET", success:function(data){ //Second API Request $.ajax({ url:'endpoint/v1/request2?id=' + data.id, type: "GET", success:function(data){ //Third API Request $.ajax({ url:'endpoint/v1/request3?name=' + data.name, type: "GET", success:function(data){ //do something with the data } }); } }); } });
Мы можем избежать ада обратных вызовов, используя промисы.
ECMAScript 2015, также известный как ES6, представил объект JavaScript Promise. Промисы в Javascript — это способ обработки асинхронных операций. Это позволяет нам возвращать значение из асинхронной функции, такой как синхронные функции. Вместо немедленного возврата окончательного значения асинхронный метод возвращает обещание предоставить значение в какой-то момент в будущем.
Но что такое обещание?
Обещание — это объект, который может находиться в одном из трех состояний:
1. Ожидание: Исходное состояние.
2. Решено/Выполнено: завершено успешно
3. Отклонено: сбой.
Говорят, что обещание выполнено, когда оно либо выполнено, либо отклонено. При возникновении любой из этих опций будет выполнен метод «тогда» или метод «поймать», связанный с обработчиком обещания.
Функции «тогда» и «поймать» принимают функцию/обратный вызов в качестве параметра, когда обещание выполнено или отклонено соответственно.
Чтобы объяснить, как работает обещание, давайте возьмем пример с «fetch». Метод fetch()
современный и универсальный, поэтому мы начнем с него. Он не поддерживается старыми браузерами, но очень хорошо поддерживается современными.
Основной синтаксис:
let promise = fetch("url/v1/endpoint");
поэтому fetch возвращает обещание, Javascript не ждет, пока обещание не будет выполнено. Он переходит на следующую строку. Но когда обещание разрешено/отклонено, мы можем что-то с ним сделать.
fetch("url/v1/endpoint") .then(data => console.log(data)) .catch(err => console.err(err));
Если вы заглянете в документацию fetch, то заметите, что данные, возвращаемые fetch, разрешаются с помощью объекта встроенного класса Response.
Нам нужно разобрать объект ответа как JSON (мы можем анализировать во многие другие форматы, такие как текст, formData, blob и т. д., но для простоты я использую JSON)
для этого нам нужно использовать response.json(data). Это также возвращает промис.
Промисы хороши тем, что теперь мы можем связывать промисы в единую цепочку, в отличие от обратных вызовов. Если какое-либо обещание будет отклонено, оно перейдет к функции catch.
fetch("url/v1/endpoint") //Resolving fetch() promise .then((data) => { return data.json() }) //Resolving data.json() promise. .then((jsonData) => console.log(jsonData.someKey)); //Catch reject from all the promises above. .catch(err => console.err(err));
Угадайте, что!
Вы также можете создать свой собственный объект Promise!! Давайте рассмотрим это. Если вы веб-разработчик, скорее всего, вы не сталкиваетесь с созданием объектов промисов так часто, но это поможет нам немного лучше понять промисы.
Возьмем простой пример. Мы хотели бы создать абзац со случайным текстом после задержки в несколько секунд каждый раз, когда страница загружается/обновляется.
Мы можем сделать это с помощью функции setTimeout().
function setup(){ noCanvas(); setTimeout(saySomething, 5000); } function saySomething() { createParagraph("SOMETHINGGGGG"); }
Теперь я оберну setTimeout в функцию, которая возвращает объект обещания, и когда обещание будет разрешено после упомянутой задержки, на экране будет виден абзац. Я также хотел включить часть отклонения обещания, поэтому ввел ошибка, если аргумент времени для функции задержки не является числом.
function setup(){ noCanvas(); // prints SOMETHINGGGGG after 5 seconds delay(5000) .then(() => saySomething()) .catch(err) => console.error(err)); // promise is rejected and console log shows Delay time needs to be a number. delay('xxx') .then(() => saySomething()) .catch(err) => console.error(err)); } function delay(time) { return new Promise((resolve, reject) => { if(isNaN(time)) { reject(new Error('Delay time needs to be a number.')); }else{ setTimeout(resolve, time); } }); } function saySomething() { createParagraph("SOMETHINGGGGG"); }
Я предполагаю, что я хоть чуть-чуть помог вам с обещаниями. Давайте рассмотрим еще несколько концепций, связанных с обещаниями, таких как статические методы Promise.all и Promise.race.
Promise.all
Допустим, есть ситуация, когда вам нужно дождаться успешного разрешения всех промисов, чтобы что-то с ними сделать. Вы можете сделать это с помощью Promise.all.
Promise.all принимает несколько объектов обещаний в качестве аргументов и возвращает обещание. Это обещание разрешается только тогда, когда разрешаются все обещания, данные в массиве. Полученные данные представляют собой массив результатов всех разрешенных промисов.
const promise = Promise.all([promise1, promise2, promise3]); promise.then((allData) => { allData.forEach((data) => { console.log(data); }); });
Обратите внимание, что Promise.all имеет быстрое поведение при сбое. Если какое-либо из промисов отклонено, то Promise.all будет отклонен в нужный момент и не будет продолжать разрешать следующие промисы в очереди.
let p1 = new Promise(function(resolve, reject) { setTimeout(resolve, 1000, 'p1'); }); let p2 = new Promise(function(resolve, reject) { setTimeout(resolve, 1200, 'p3'); }); let p3 = new Promise(function(resolve, reject) { setTimeout(reject, 300, 'p4'); }); let p4 = new Promise(function(resolve, reject) { setTimeout(resolve, 800, 'p5'); }); let promise = Promise.all([p1, p2, p3, p4]); promise.then(function(data) { data.forEach(function(data) { console.log(data); }); }) .catch(function(error) { console.error('error', error); }); //Console logs error p4 //Reason: p3 is the one with least time out and gets rejected first. //so it does not try to resolve all other promises.
Если вы хотите продолжать, даже если несколько обещаний отклонено, то вы можете иметь подвох для каждого из них в отдельности.
let promise = Promise.all([p1.catch(function() {}), p2.catch(function() {}), p3.catch(function() {}), p4.catch(function() {}) ]); promise.then(function(data) { data.forEach(function(data) { console.log(data); }); }) .catch(function(error) { console.error('error', error); }); //Console logs: //p1 //p2 //undefined //p4
Если в массиве есть обещание, которое должно вернуть другое обещание, выполнив другой запрос API или что-то еще, вы можете использовать .then
на обещании, из которого вы хотите вернуть другое обещание.
Рассмотрим сценарий:
Пользовательские данные, которые разбросаны по нескольким базам данных (db1, db2, db3), есть одна основная база данных с информацией о том, какая база данных содержит информацию о каком пользователе.
db1, d2 , базы данных db3 содержат идентификатор, имя пользователя, информацию об электронной почте.
Хотя существует еще одна база данных Arch, которая содержит информацию о профиле пользователя, такую как имя, фамилия, адрес, страна, почтовый индекс и т. д.
Наша задача состоит в том, чтобы создать полный пользовательский объект, содержащий всю информацию вместе, и возвращать объект только тогда, когда объект завершен.
Кроме того, если какой-либо из вызовов API будет отклонен, мы хотели бы знать, где он находится. неуспешный.
export type user { id:number, username:string, email:string, firstname:string, lastname:string, address:string, country:string, postalCode:string, }
Давайте напишем для него решение, используя все знания, которые мы знаем о Promise.all.
const main = require('./main'); const db1 = require('./db1'); const db2 = require('./db2'); const db3 = require('./db3'); const arch = require('./arch'); module.exports = function(id) { const dbs = { db1, db2, db3, }; const promise = Promise.all([ main(id) .catch(() => { //individual reject handling return Promise.reject("Error Main") }) .then((db) => { //Individual resolves can also be handled return dbs[db](id); //Returns a new promise. }) .catch(() => { //individual reject handling for dbs[db](id) promise return Promise.reject('Error ' + db); }), arch(id) .catch(() => { //individual reject handling return Promise.reject("Error Arch") }) ]); return promise .then((data) => ({ id: id, username: data[0].username, email: data[0].country, firstname: data[1].firstname, lastname: data[1].lastname, address:data[1].address, country: data[1].country, postalCode: data[1].postalCode })) .catch((err) => { console.log(err) throw err; }) };
Я думаю, что этого было достаточно для Promise.all.
Давайте перейдем к Promise.race.
Это несколько противоположно Promise.all.
Promise.race принимает массив обещаний. Результатом является новое обещание, которое разрешается или отклоняется, как только одно из обещаний разрешается или отклоняется.
function delay(time) { return new Promise((resolve, reject) => { setTimeout(resolve,time, 'Success ' + time); }); } Promise.race(delay(1000), delay(100)) .then((data) => console.log(data) ) .catch((err) => console.error(err)); //console log will show: Success 100 //As that was the first promise which was resolved.
Далее давайте посмотрим на async/await
, который стал частью ES8 в 2017 году. async/await
— не новая функция. Это новый способ написать асинхронную функцию, которая возвращает обещание.
async
— это ключевое слово для объявления функции как асинхронной, и в теле этой функции мы можем использовать ключевое слово await
.
Асинхронные функции могут содержать ноль или более await
выражений. Выражения ожидания создают впечатление, что они выполняются синхронно, приостанавливая выполнение до тех пор, пока обещание не будет выполнено (выполнено/отклонено). Разрешенное значение обещания рассматривается как возвращаемое значение выражения ожидания.
function delay(time) { return new Promise((resolve, reject) => { setTimeout(resolve("resolved"), time); }); } async function asyncCall() { console.log('calling'); const result = await delay(2000); console.log(result); // Expected output: "resolved" } asyncCall(); //Console log prints "resolved" after 2 seconds
использование async/await
позволяет нам использовать try
/ catch
блоков вокруг асинхронного кода. Что было невозможно, пока мы не использовали промисы.
Давайте вернемся к примеру с выборкой, который я использовал в начале промисов, и заставим его работать с асинхронным ожиданием.
function setup() { getData() .then((data) => console.log(data)) .catch((err) => console.error(err)); } async function getData() { const data = await fetch("url/v1/endpoint"); const jsonData = await data.json(); return jsonData; }
Чтобы лучше понять async/await
, давайте попробуем повторить пример вызовов jQuery ajax API, который я использовал при объяснении обратных вызовов с использованием async
и await
и fetch
.
async function getResult(){ const response1 = await fetch("endpoint/v1/request1"); const response2 = await fetch("endpoint/v1/request2?id="+response1.id); const response3 = await fetch("endpoint/v1/request3?name="+response2.name); return { id:response1.id, name:response2.name, address:response3.address }; } function printResult() { getResult() .then((data) => console.log(data)) .catch((err) => console.error(err)); }
Посмотрите, каким плоским, лаконичным и простым выглядит код, если сравнить его с эпохой обратного вызова. Это также становится легко понять.
Я надеюсь, что этим блогом я немного облегчил вам жизнь.
Я пытаюсь разобраться во многих других концепциях javascript и надеюсь написать о них.
Пожалуйста, оставляйте мне свои ценные комментарии и предложения, они обязательно помогут в моих поисках.