Асинхронные вызовы
Но использование reduce a.k.a fold для суммирования списков — очень упрощенный пример. Идея гораздо мощнее. Давайте разберем еще один пример.
Одна из проблем использования Javascript в браузере заключается в том, что все выполняется в одном потоке, и поэтому мы должны использовать коллбеки.
Задача.
- Загрузить несколько скриптов
- Склеить их
- сохранить порядок скриптов при склейке
То есть надо написать функцию примерно такого вида:
combine(["/jquery.js", "/underscore.js", "/backbone.js"], function(content){ // content должен содержать все скрипты, склеенные в правильном порядке. });
Напишем реализацию функции combine. Сначала — лобовой подход.
function combine(scripts, callback){ var data []; for(var i =0; l = scripts.length; i< l; ++i){ // .... } }
Для получения скриптов было бы логично использовать jQuery.ajax:
function combine(scripts, callback){ var data []; for(var i =0; l = scripts.length; i< l; ++i){ jQuery.ajax({ url: scripts[i], success : function(response){ // .... } }); } }
Подобный код не будет тормозить браузер, поскольку запросы к серверу будут отправлены асинхронно. То есть при выполнении будет 3 параллельных запроса.
Напишем обработчик для успешного скачивания скрипта.
function combine(scripts, callback){ var data []; for(var i =0; l = scripts.length; i< l; ++i){ jQuery.ajax({ url: scripts[i], success : function(response){ data[i] = response; if(data.length === scripts.length){ callback(data.join("")); } } }); } }
Вроде бы функция готова. Но есть два но.
Во-первых уродливо, во-вторых — оно не будет работать.
С чем тут могут быть проблемы? С областями видимости Javascript. В этом языке область видимость не поблочная, а функциональная. то есть все 3 функции будут видеть одно и то значение переменной i. Поскольку цикл отработает раньше, чем придут ответы от сервера, все три функции будут работать с i == 3;
Эта проблема решается стандартным способом — мы кэшируем значение переменной цикла. Но нельзя сказать, что код от этого стал красивее.
function combine(scripts, callback){ var data []; for(var i =0; l = scripts.length; i< l; ++i){ (function (i){ jQuery.ajax({ url: scripts[i], success : function(response){ data[i] = response; if(data.length === scripts.length){ callback(data.join("")); } } }); }(i)); } }
Почти даже работает. Для того, чтобы избавиться от замыканий и хитрых переменных, можно использовать foreach
function combine(scripts, callback){ var data []; scripts.forEach(function(script,i){ jQuery.ajax({ url: scripts[i], success : function(response){ data[i] = response; if(data.length === scripts.length){ callback(data.join("")); } } }); }); } }
Лучше, конечно, но все равно страшновато. Кстати, код все еще не будет работать правильно. Его можно допилить до работоспособного состояния, но это создаст дополнительные сложности и в разработке и в последующей поддержке.