Автор оригинала: Scott Robinson.
Вступление
Я признаю, что я был одним из тех людей, которые решили изучать Node.js просто из-за густогата вокруг него и насколько все все говорили об этом. Я подумал, что на нем должно быть что-то особенное, если у него есть эта большая поддержка так рано в своей жизни. Я в основном пришел из фона C, Java и Python, поэтому асинхронный стиль JavaScript был сильно отличаться от всего, что я встречал раньше.
Поскольку многие из вас, наверное, знают, все JavaScript действительно находится внизу – это однопоточная петля событий, которая обрабатывает очередные события. Если вы должны были выполнить длинную рабочую задачу в пределах одной резьбы, то процесс будет блокировать, заставляя другие события, чтобы дождаться, чтобы обрабатываться (I.e. UI зависает, данные не сохраняются, и т. Д.). Это именно то, что вы хотите избежать в системе, управляемой событиями. Здесь Это отличное видео, объясняющее гораздо больше о петле событий JavaScript.
Чтобы решить эту проблему блокировки, JavaScript сильно опирается на обратные вызовы, которые являются функциями, которые выполняются после длительного проработанного процесса (IO, TIMER, ITC), что позволит выполнить выполнение кода, чтобы продолжить длительное задание.
downloadFile('example.com/weather.json', function(err, data) { console.log('Got weather data:', data); });
Проблема: обратный ада
Хотя концепция обратных вызовов велика в теории, она может привести к тому действительно запутанному и сложному коду. Просто представьте, если вам нужно сделать обратный вызов после обратного вызова:
getData(function(a){ getMoreData(a, function(b){ getMoreData(b, function(c){ getMoreData(c, function(d){ getMoreData(d, function(e){ ... }); }); }); }); });
Как видите, это может действительно выйти из-под контроля. Бросить немного Если
заявления, для
Петли, функциональные звонки или комментарии, и у вас будет очень сложный код. Начинающие особенно падают жертвам этого, не понимая, как избежать этой «пирамиды гибели».
Альтернативы
Дизайн вокруг него
Так много программистов попадают в обратную связь ада из-за этого (плохого дизайна) в одиночку. Они на самом деле не думают о своей структуре кодовой структуры заранее, и не понимают, насколько плохой их код дошел до тех пор, пока не стал слишком поздно. Как и в случае с любым кодом, вы пишете, вы должны остановиться и подумать о том, что можно сделать, чтобы сделать его проще и читабелее до или во время, написание его. Вот несколько советов, которые вы можете использовать для Избегайте обратного вызова ада (или, по крайней мере, управлять этим).
Используйте модули
Примерно в каждом языке программирования один из лучших способов уменьшения сложности – это модульзы. Программирование JavaScript ничем не отличается. Всякий раз, когда вы пишете код, возьмите некоторое время, чтобы вернуть назад и выяснить, есть ли общий шаблон, который вы часто встречаете.
Вы пишете один и тот же код несколько раз в разных местах? Разные части вашего кода следуют общей теме? Если это так, у вас есть возможность почистить вещи и абстрактные и повторно использовать код.
Там есть тысячи модулей, вы можете посмотреть на ссылку, но вот несколько рассмотреть. Они обрабатывают общие, но очень специфические задачи, которые в противном случае будут беспокоить ваш код и уменьшить читаемость: Плюрализировать , CSV , QS , клон Отказ
Дайте свои функции имена
При чтении кода (особенно грязный, неорганизованный код), его легко потерять отслеживание логического потока или даже синтаксиса, когда небольшие пробелы перегружены так много вложенных обратных вызовов. Один из способов помочь борьбе состоит в том, чтобы назвать ваши функции, так что все, что вам придется сделать, это взглянуть на имя, и вы будете лучшим представлением о том, что он делает. Это также дает вам ваши глаза синтаксической ориентиры.
Рассмотрим следующий код:
var fs = require('fs'); var myFile = '/tmp/test'; fs.readFile(myFile, 'utf8', function(err, txt) { if (err) return console.log(err); txt = txt + '\nAppended something!'; fs.writeFile(myFile, txt, function(err) { if(err) return console.log(err); console.log('Appended text!'); }); });
Глядя на это может занять вам несколько секунд, чтобы понять, что делает каждый обратный вызов и где он начинается. Добавление небольшого дополнительной информации (имена) в функции могут иметь большую разницу для читаемости, особенно когда вы многоугольники глубоко в обратных вызовах:
var fs = require('fs'); var myFile = '/tmp/test'; fs.readFile(myFile, 'utf8', function appendText(err, txt) { if (err) return console.log(err); txt = txt + '\nAppended something!'; fs.writeFile(myFile, txt, function notifyUser(err) { if(err) return console.log(err); console.log('Appended text!'); }); });
Теперь просто быстрый взгляд скажет вам, что первая функция добавляет некоторый текст, а вторая функция уведомляет пользователя из изменения.
Объявить ваши функции заранее
Один из лучших способов уменьшить кодовый беспорядок – поддержание лучшего разделения кода. Если вы заявляете функцию обратного вызова заранее и позвоните позже, вы избежите глубоко вложенные структуры, которые делают обратный ад, с которым так сложно работать.
Так что вы могли бы пойти от этого …
var fs = require('fs'); var myFile = '/tmp/test'; fs.readFile(myFile, 'utf8', function(err, txt) { if (err) return console.log(err); txt = txt + '\nAppended something!'; fs.writeFile(myFile, txt, function(err) { if(err) return console.log(err); console.log('Appended text!'); }); });
…к этому:
var fs = require('fs'); function notifyUser(err) { if(err) return console.log(err); console.log('Appended text!'); }; function appendText(err, txt) { if (err) return console.log(err); txt = txt + '\nAppended something!'; fs.writeFile(myFile, txt, notifyUser); } var myFile = '/tmp/test'; fs.readFile(myFile, 'utf8', appendText);
Хотя это может быть отличным способом облегчить проблему, она не полностью решает проблему. При чтении кода написано таким образом, если вы не помните именно то, что делает каждую функцию, то вам придется вернуться назад и посмотреть на каждого, чтобы вернуть логический поток, который может занять время.
Async.js.
К счастью, библиотеки, как Async.js существовать, чтобы попытаться бореть проблему. Async добавляет тонкий слой функций сверху вашего кода, но может значительно уменьшить сложность, избегая вложенности обратного вызова.
Многие методы помощника существуют в Async, который можно использовать в разных ситуациях, таких как Серия , параллельно , Водопад и т. Д. Каждая функция имеет определенный случай для использования, поэтому потребуется некоторое время, чтобы узнать, кто поможет, в каких ситуациях.
Так же хорошо, как Async, как и все, его не идеально. Его очень легко увлечься, объединяя серии, параллель, навсегда и т. Д., В какой момент вы вернитесь туда, где вы начали с грязным кодом. Будьте осторожны, чтобы не преждевременно оптимизировать. Просто потому, что несколько асинковых задач могут быть запущены параллельно, не всегда означает, что они должны. На самом деле, поскольку узел – это только однопоточные, работающие задачи параллельно на использовании Async, имеет мало, не имеет большого усиления производительности.
Код сверху может быть упрощен с использованием водопада Async:
var fs = require('fs'); var async = require('async'); var myFile = '/tmp/test'; async.waterfall([ function(callback) { fs.readFile(myFile, 'utf8', callback); }, function(txt, callback) { txt = txt + '\nAppended something!'; fs.writeFile(myFile, txt, callback); } ], function (err, result) { if(err) return console.log(err); console.log('Appended text!'); });
Обещания
Хотя обещания могут занять немного, чтобы понять, на мой взгляд, они одна из самых важных концепций, которые вы можете учиться в JavaScript. Во время развития одного из моих Saas Apps Я закончил переписать всю кодовую базу, используя обещания. Он не только резко уменьшил количество строк кода, но он сделал логический поток кода гораздо проще для того, чтобы следовать.
Вот пример, используя очень быстрое и очень популярную библиотеку обещания, Bluebird :
var Promise = require('bluebird'); var fs = require('fs'); Promise.promisifyAll(fs); var myFile = '/tmp/test'; fs.readFileAsync(myFile, 'utf8').then(function(txt) { txt = txt + '\nAppended something!'; fs.writeFile(myFile, txt); }).then(function() { console.log('Appended text!'); }).catch(function(err) { console.log(err); });
Обратите внимание, как это решение не только короче, чем предыдущие решения, но легче прочитать а также (хотя, хотя, по общему признанию, код стиля для обещания может принять некоторое использование). Потратьте время, чтобы учиться и понимать обещания, это будет стоить вашего времени. Однако обещания определенно не являются решением всех наших проблем в асинхронном программировании, поэтому не предполагайте, используя их, у вас будет быстрое, чистое, без ошибок приложение. Ключ знает, когда они будут полезны для вас.
Несколько обещаний библиотек, которые вы должны проверить, являются Q , Bluebird или Встроенные обещания Если вы используете ES6.
Async/a ждать
Примечание. Это функция ES7, которая в настоящее время не поддерживается в узле или IO.JS. Тем не менее, вы можете использовать его прямо сейчас с транспортировкой, как Варить .
Другой вариант для очистки вашего кода, и мой скорее – любимый (когда он имеет более широкую поддержку), использует async
Функции. Это позволит вам написать код, который выглядит намного больше похоже на синхронный код, но все же асинхронный.
Пример:
async function getUser(id) { if (id) { return await db.user.byId(id); } else { throw 'Invalid ID!'; } } try { let user = await getUser(123); } catch(err) { console.error(err); }
db.user.ByID (ID)
Звонок возвращает Обещание
, который бы мы обычно должны были использовать с .then ()
, но с ждать
Мы можем напрямую вернуть разрешенное значение.
Обратите внимание, что функция, содержащая ждать
Звонок префиксирован с async
, что говорит нам, что он содержит асинхронный код и также должен быть вызван ждать
Отказ
Еще одно большое преимущество для этого метода мы можем сейчас использовать попробуйте/поймать
, для
и в то время как
С нашими асинхронными функциями, которые гораздо интуитивно понятны, чем цепочка, обещающие вместе.
Помимо использования трансполеров, таких как Babel и Traceur , вы также можете получить такую функциональность в узле с Asyncawait упаковка.
Заключение
Избегайте таких распространенных проблем, поскольку обратный вызов ада не легко, поэтому не ожидайте прекратить ваши разочарование сразу. Мы все пойманы в этом. Просто попробуйте замедлить и занять некоторое время, чтобы подумать о структуре вашего кода. Как и все, практика делает идеально.
Вы записались на обратный звонок? Если это так, как вы обходите вокруг этого? Сообщите нам в комментариях!