Эта статья была впервые опубликована на платформе общедоступной учетной записи WeChat (itclancoder), если вы хотите получить лучший опыт чтения, вы можете нажать на ссылкуад обратного звонка
из предыдущей статьивы действительно понимаете обратные вызовыМы уже знаем, что функция обратного вызова должна полагаться на другую функцию для выполнения вызова. Она выполняется асинхронно, то есть требует времени ожидания. Типичным примером являются приложения Ajax, такие как http-запросы, без обновления браузера, когда вы выполнить Когда происходит событие DOM, такое как щелчок по ссылке на странице, возврат каретки и другие операции с событиями, браузер незаметно отправит на сервер несколько http-запросов с идентифицируемыми параметрами в фоновом режиме и будет ждать сервер. для ответа и возврата данных. Этот процесс является асинхронным обратным вызовом. Многие функции необходимо вызывать непрерывно, и когда они взаимозависимы, это похоже на следующий код. Весь код вложен слой за слоем, что выглядит огромным и отвратительным, в результате в аду обратных вызовов. В этой статье вы узнаете, как избежать ада обратных вызовов, в этой статье вы узнаете следующее:
-
Что такое callback hell (функции вложены как параметры)
-
Что такое функция обратного вызова (функция в качестве параметра должна полагаться на другую функцию для выполнения вызова)
-
Как исправить ад обратного вызова
-
Делайте свой код коротким (давайте осмысленные имена функциям и знайте, что они означают, а не анонимные функции, которые пишутся большими кусками).
-
Модульность (инкапсуляция функций, упаковка, каждая функция независима, можно определить js-файл Vue отдельно, импорт через импорт в реакции — это проявление)
-
Обработка каждой ошибки
-
Некоторые практические правила при создании модулей
-
Промисы/Генераторы/ES6 и т.д.
-
-
Обещания: способ написания асинхронного кода, который по-прежнему выполняется сверху вниз и обрабатывает больше типов ошибок из-за поощрения обработки ошибок в стиле try/catch.
-
Генераторы: Генераторы позволяют «приостановить» одну функцию, не приостанавливая состояние всей программы, но код немного сложнее, так что кажется, что код выполняется сверху вниз.
-
Асинхронные функции: асинхронные функции — это предлагаемая функция ES7, которая дополнительно обернет генераторы и наследование в синтаксис более высокого уровня.
Что такое «ад обратного вызова»?
Асинхронный JavaScript или используйте обратный вызов JavaScript, сложно интуитивно получить правильные результаты. Конечная партия кода выглядит так:
fs.readdir(source, function (err, files) {
if (err) {
console.log('Error finding files: ' + err)
} else {
files.forEach(function (filename, fileIndex) {
console.log(filename)
gm(source + filename).size(function (err, values) {
if (err) {
console.log('Error identifying file size: ' + err)
} else {
console.log(filename + ' : ' + values)
aspect = (values.width / values.height)
widths.forEach(function (width, widthIndex) {
height = Math.round(width / aspect)
console.log('resizing ' + filename + 'to ' + height + 'x' + height)
this.resize(width, height).write(dest + 'w' + width + '_' + filename, function(err) {
if (err) console.log('Error writing file: ' + err)
})
}.bind(this))
}
})
})
}
})
увидеть окончательную форму пирамиды и все })? Это ласково называют адом обратного вызова
Причина ада обратных вызовов заключается в том, что люди пытаются написать JavaScript таким образом, чтобы он выполнялся визуально сверху вниз.. Эту ошибку совершают многие! В других языках, таких как C, Ruby или Python,Ожидайте, что все, что происходит в строке 1, будет выполнено до того, как начнет выполняться код в строке 2, и так далее.. Как вы узнаете, JavaScript отличается
Что такое функция обратного вызова?
Обратные вызовы — это просто условное обозначение для использования функций JavaScript. В языке JavaScript нет ничего особенного, называемого «обратными вызовами», это просто соглашение. В отличие от большинства функций, которые немедленно возвращают некоторый результат, использование функции обратного вызова требует некоторого времени для получения результата. Означает «это займет некоторое время» или «произойдет в будущем, а не сейчас». Обычно обратные вызовы используются только при выполнении ввода-вывода, например при загрузке чего-либо, чтении файла, взаимодействии с базой данных и т. д.
Когда вы вызываете обычную функцию, вы можете использовать ее возвращаемое значение
var result = multiplyTwoNumbers(5, 10)
console.log(result // 50 gets printed out
Однако асинхронные функции и функции, использующие обратные вызовы, ничего не возвращают немедленно.
var photo = downloadPhoto('http://coolcats.com/cat.gif')
// photo is 'undefined'!
В этом случае загрузка gif может занять много времени, и вы не хотите, чтобы программа останавливалась() в ожидании завершения загрузки.
Вместо этого вы сохраняете код, который должен выполняться после завершения загрузки функции. Это обратный звонок! Вы передаете его функции downloadPhoto, и она выполнит ваш обратный вызов, когда загрузка будет завершена (например, «позвоню вам позже»), и передаст фотографию (или ошибку, если есть ошибка)
downloadPhoto('http://coolcats.com/cat.gif', handlePhoto)
function handlePhoto (error, photo) {
if (error) console.error('Download error!', error)
else console.log('Download finished', photo)
}
console.log('Download started')
Самое большое препятствие, с которым сталкиваются люди, пытаясь понять обратные вызовы, — это понимание порядка, в котором выполняется программа при ее запуске. В этом примере происходят три вещи. Сначала объявите функцию handlePhoto, затем вызовите функцию downloadPhoto и передайте handlePhoto в качестве функции обратного вызова и, наконец, распечатайте «Загрузка началась».
Обратите внимание, что handlePhoto еще не был вызван, он только что был создан и передан в качестве обратного вызова для downloadPhoto. Но он не запустится, пока downloadPhoto не завершит свою задачу, что может занять много времени в зависимости от скорости вашего интернет-соединения.
Этот пример иллюстрирует две важные концепции
- Обратный вызов handlePhoto — это просто способ сохранить что-то позже.
- Порядок, в котором происходят действия, не читается сверху вниз, а перескакивает в зависимости от того, когда что-то сделано.
Как я могу исправить ад обратного вызова?
Ад обратного вызова вызван плохой практикой кодирования. К счастью, написать лучший код несложно! Просто нужно следовать трем правилам:
1. Держите свой код коротким
Вот какой-то беспорядочный браузерный JavaScript, который использует запросы браузера для отправки запросов AJAX на сервер.
var form = document.querySelector('form')
form.onsubmit = function (submitEvent) {
var name = document.querySelector('input').value
request({
uri: "http://example.com/upload",
body: name,
method: "POST"
}, function (err, response, body) {
var statusMessage = document.querySelector('.status')
if (err) return statusMessage.value = err
statusMessage.value = body
})
}
Этот код имеет две анонимные функции. Давайте дадим им имена formbubmit и postresponse
var form = document.querySelector('form')
form.onsubmit = function formSubmit (submitEvent) {
var name = document.querySelector('input').value
request({
uri: "http://example.com/upload",
body: name,
method: "POST"
}, function postResponse (err, response, body) {
var statusMessage = document.querySelector('.status')
if (err) return statusMessage.value = err
statusMessage.value = body
})
}
Как видите, именованные функции очень просты и имеют некоторые непосредственные преимущества.
- Облегчает чтение кода благодаря описательным именам функций.
- Когда возникает исключение, вы получите трассировку стека, ссылающуюся на фактическое имя функции, а не на «анонимное».
- Позволяет перемещать функции и ссылаться на них по имени
Теперь мы можем перенести эти функции на верхний уровень нашей программы.
document.querySelector('form').onsubmit = formSubmit
function formSubmit (submitEvent) {
var name = document.querySelector('input').value
request({
uri: "http://example.com/upload",
body: name,
method: "POST"
}, postResponse)
}
function postResponse (err, response, body) {
var statusMessage = document.querySelector('.status')
if (err) return statusMessage.value = err
statusMessage.value = body
}
Обратите внимание, что объявления функций здесь определены в нижней части файла. Это благодаря функции повышения
2. Модульный
Это самая важная часть: каждый может создавать модули. Цитируя Исаака Шлютера (из проекта node.js): «Напишите небольшой модуль, каждый из которых выполняет одну задачу, а затем соберите их в другие модули, выполняющие более важные задачи. Если вы не пойдете туда, вы не сможете получить в аду обратного вызова
Давайте возьмем приведенный выше шаблонный код и разделим его на несколько файлов, чтобы превратить в модуль. Я покажу шаблон модуля, который работает для кода браузера или кода сервера (или для обоих)
Это новый файл с именем formuploader.js, который содержит две наши предыдущие функции.
module.exports.submit = formSubmit
function formSubmit (submitEvent) {
var name = document.querySelector('input').value
request({
uri: "http://example.com/upload",
body: name,
method: "POST"
}, postResponse)
}
function postResponse (err, response, body) {
var statusMessage = document.querySelector('.status')
if (err) return statusMessage.value = err
statusMessage.value = body
}
Бит module.exports является примером модульной системы node.js, которая работает в узле, Electron и браузерах, использующих browserify. Мне очень нравится этот шаблон, потому что он работает где угодно, очень прост для понимания и не требует сложных конфигурационных файлов или скриптов.
Теперь, когда у нас есть formuploader.js (и мы загружаем его на страницу как тег script в браузере), он нам просто нужен и используется! Вот как сейчас выглядит код нашего приложения
var formUploader = require('formuploader')
document.querySelector('form').onsubmit = formUploader.submit
Теперь наше приложение состоит всего из двух строк кода и имеет следующие преимущества:
- Легче понять для новых разработчиков - они не увязают, читая все функции formuploader
- ormuploader можно использовать в другом месте без дублирования кода, и его легко опубликовать на github или npm.
3. Обрабатывайте каждую ошибку
Существуют разные типы ошибок: синтаксические ошибки, вызванные программистом (обычно, когда вы пытаетесь запустить программу в первый раз), ошибки времени выполнения, вызванные программистом (код запустился, но есть ошибка, которая что-то испортила), ошибки платформы вызваны бесполезными правами доступа к файлам, сбоем жесткого диска, отсутствием сетевого подключения и т. д. Эта часть предназначена только для устранения последнего типа ошибок.
Первые два правила в основном касаются того, чтобы ваш код был удобочитаемым, но в то же время они делают ваш код стабильным. При обработке обратного вызова вы обрабатываете отправленную задачу по определению, делаете что-то в фоновом режиме и либо завершаете успешно, либо прерываете ее из-за сбоя. Любой опытный разработчик скажет вам, что вы никогда не знаете, когда появятся эти ошибки, поэтому вы должны их планировать.
С обратными вызовами наиболее распространенным способом обработки ошибок является способ Node.js, где первый параметр обратного вызова всегда зарезервирован для ошибок.
var fs = require('fs')
fs.readFile('/Does/not/exist', handleFile)
function handleFile (error, file) {
if (error) return console.error('Uhoh, there was an error', error)
// otherwise, continue on and use `file` in your code
}
Наличие первого аргумента является ошибкой — это простое соглашение, которое побуждает вас не забывать обрабатывать свои ошибки. Если это второй параметр, вы можете написать код, подобный функции handleFile(file){}, и тогда ошибки будет проще игнорировать.
Кодовую базу также можно настроить так, чтобы вы не забывали обрабатывать ошибки обратного вызова. Самый простой в использовании называется стандартным. Все, что вам нужно сделать, это запустить $standard в вашей папке с кодом, и он покажет вам каждый обратный вызов в вашем коде с необработанными ошибками.
резюме
- Не вкладывать функции. дайте им имена и поместите их на верхний уровень программы
- Используйте подъем функций в своих интересах, чтобы перемещать функции
- Обрабатывайте каждую ошибку в каждом обратном вызове. Используйте стандарты, которые помогут вам
- Создавайте повторно используемые функции и размещайте их в модулях, чтобы уменьшить когнитивную нагрузку, необходимую для понимания кода. Разделите свой код на небольшие части. Это также поможет вам обрабатывать ошибки, писать тесты, заставлять вас создавать стабильный и документированный общедоступный API для вашего кода и помогает с рефакторингом.
Наиболее важным аспектом предотвращения ада обратных вызовов является удаление функциональности с пути, чтобы легче понять ход программы.Что, и не нужны все детали, связанные с функцией новичка, чтобы понять, что программа пытается сделать
Вы можете сначала переместить функции в конец файла, затем переместить их в другой файл с соответствующим требованием, например, require('./photo-helpers.js'), а затем переместить их в отдельный модуль, например, require('image- изменить размер))
Вот несколько практических правил при создании модулей:
- Сначала переместите повторно используемый код в функцию
- Когда ваши функции (или группа функций, связанных с одной и той же темой) станут достаточно большими, переместите их в другой файл и откройте его с помощью module.exports. Вы можете использовать относительные требования, чтобы загрузить его
- Если у вас есть код, который можно использовать в нескольких проектах, предоставьте ему собственный файл readme, тесты и package.json и опубликуйте его на github и npm. У конкретных методов, перечисленных здесь, так много замечательных преимуществ.
- Хороший модуль небольшой и сосредоточен на одной проблеме
- Один файл в модуле не должен превышать 150 или около того строк JavaScript.
- Модуль не должен иметь более одной папки на уровне вложенных папок. Если да, то, вероятно, он делает слишком много
- Попросите более опытных программистов, которых вы знаете, показать вам примеры хороших модулей, пока у вас не будет хорошего представления о том, как они выглядят. Если требуется несколько минут, чтобы понять, что происходит, вероятно, это не очень хороший модуль.
Как насчет промисов/генераторов/ES6 и т.д.
Прежде чем рассматривать более продвинутые решения, имейте в виду, что обратные вызовы являются фундаментальной частью JavaScript (поскольку они всего лишь функции), и вы должны научиться читать и писать их, прежде чем изучать более сложные языковые функции, поскольку все они зависят от вызова обратных вызовов. . Если вы еще не можете написать поддерживаемый код обратного вызова, используйте его.
Если вы действительно хотите, чтобы ваш асинхронный код читался от начала до конца, вы можете попробовать что-нибудь необычное. Обратите внимание, что это может привести к проблемам с производительностью и/или кросс-платформенной совместимостью во время выполнения.
- Promises: это способ написания асинхронного кода, который по-прежнему выполняется сверху вниз и обрабатывает больше типов ошибок из-за поощрения обработки ошибок в стиле try/catch.
- GeneratorsГенераторы позволяют «приостановить» одну функцию, не приостанавливая состояние всей программы, но код немного сложнее, чтобы создать впечатление, что код выполняется сверху вниз.
- Async functionsАсинхронные функции — это предлагаемая функция ES7, которая дополнительно оборачивает генераторы и промисы в синтаксис более высокого уровня.
Суммировать
Основная причина ада обратных вызовов заключается в том, что существует слишком много уровней вложенности кода функциональной логики, что приводит к снижению удобочитаемости и сложности сопровождения.Самый важный аспект предотвращения ада обратных вызовов заключается в том, чтобы переместить функции, сохранить код простым, а не вложенным и разделить на небольшой модуль, то есть выполнить большую инкапсуляцию кода, обернуть нужные свойства и методы ключевым словом function и дать ему осмысленное имя, например: всплывающее окно на странице, отображение, скрытие, удаление -down и другие функции Небольшие модули обёрнуты известными функциями, а анонимные функции используются реже, так что их можно использовать многократно, что также упрощает понимание хода программы.
В дополнение к общей функции обратного вызова, такой как асинхронная обработка, есть также промисы, генераторы и асинхронность, которые являются способами обработки асинхронной обработки. Код Лично я думаю, что это трудно понять, но код — это настоящая трансформация этих языков, Сао Нянь, давай, давай...