Статьи

JavaScript для разработчиков на C #: обратные вызовы (часть III)

В предыдущих двух статьях ( один , два ) мы исследовали использование обратных вызовов посредством создания функции mapp для массивов (так называемой, потому что последние JavaScripts уже имеют собственный метод map) и создания функции mapAsync, в которой работа выполняется асинхронно а не серийно. Причина этого состояла в том, что мы могли избежать появления предупреждения браузера «скрипт выполняется долго», а также, что более важно, предоставить пользователю отзывчивый пользовательский интерфейс.

рискфото © 2007 спутник | больше информации (через: Wylio ) В этой заключительной части я просто хочу привести в порядок метод mapAsync. Когда я оставлял это в прошлый раз, функция обрабатывала каждый элемент, используя задержку. Это, по всей вероятности,путьслишком много работы ипутислишком медленно. Мы потенциально могли бы выполнять больше работы каждый цикл, не причиняя неудобства пользователю и, таким образом, сократить количество задержек, которые нам приходится проходить. Но сколько работы? Как долго у нас есть? Якоб Нильсен говорит,что

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

Итак, 100 миллисекунд. На самом деле, если подумать, это не оставляет времени для маневра, и поэтому я возьму половину этого для метода mapAsync.

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

JavaScript поставляется с классом Date. Если вы обновляете Date, вы получаете переменную, содержащую текущую дату / время, так же, как DateTime.Now в C # и .NET. Что мы сделаем, так это определим время начала и затем начнем обрабатывать как можно больше элементов. Для каждого элемента мы его обработаем, затем получим текущее время. Если текущее время минус начальное время меньше 50 мс, по кругу мы снова идем. К сожалению, мы не можем просто вычесть одну дату из другой, оператор минус работает только для чисел. Однако не все потеряно: если мы конвертируем Date в число, мы получаем количество миллисекунд от некоторой базовой даты. А конвертировать? Просто используйте унарный оператор плюс. Вот пример:

var start = +new Date();
do {
// work
} while ((+new Date() - start) < 50);

 

Мы получаем текущую дату / время в виде количества миллисекунд, обновляя дату, а затем преобразовывая ее в число, используя унарный плюс. Затем мы входим в цикл do.. while и продолжаем выполнять работу, пока текущая дата / время минус время начала меньше 50. Довольно просто, нет?

Включение его в текущую версию mapAsync довольно обыденно:

Array.prototype.mapAsync = function (process, done, context) {
var i = 0,
result = [],
last = this.length,
self = this,
processAsync = function () {
var start = +new Date();
do {
if (i in self) {
result[i] = process.call(context, self[i], i);
}
i++;
} while ((i < last) && (+new Date() - start) < 50);
console.log("*"); // just for showing progress
if (i === last) {
done.call(context, result);
}
else {
processAsync.delay(10);
}
};
processAsync();
};

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

Конечно, мой маленький пример 8-элементного массива просто не собирается сокращать горчицу с помощью этого нового кода, поэтому давайте разберемся и используем массив из 100 000 элементов:

var myArray = [];
for (var i = 0; i < 100000; i++) {
myArray[i] = i;
}

myArray.mapAsync(function (element, index) {
return "<" + index.toString() + ": " + element.toString() + ">";
}, function (a) {
console.log("done");
delete myArray;
});

 

На этот раз все, что делает обратный вызов завершения, — это запись «выполнено» (печать массива перегрузит консоль — я знаю, потому что я это сделал) и удаление исходного массива — он довольно большой. Когда я запускаю этот код в Firebug, я получаю это:

*
undefined
*
*
*
*
*
*
*
*
*
done

Опять же, «неопределенный» в верхней части — это Firebug, печатающий возвращаемое значение вызова mapAsync. Как видите, он разбил массив из 100 000 элементов на 10 отложенных вызовов, примерно 10 000 элементов на вызов. Или, если хотите, мне удалось обработать 10 000 элементов за 50 миллисекунд, что не так уж и плохо.

Вот и все для этой серии о обратных вызовах. Я надеюсь, что это оказалось полезным. Помните: если вы хотите использовать этот метод mapArray (я использую лицензию MIT, так что дерзайте), выньте оператор logging посередине.