Автор: Марин Хавербеке
Дата: 24 июля 2007
Функция вызова с текущим продолжением схемы позволяет захватить вычисление, состояние стека вызовов, как это было, и возобновить то же самое состояние в более позднее время. Вдобавок к такому примитиву могут быть реализованы различные формы обработки исключений и трюки типа longjmp на языке C.
function traverseDocument(node, func) {
func(node);
var children = node.childNodes;
for (var i = 0; i < children.length; i++)
traverseDocument(children[i], func);
}
function capitaliseText(node) {
if (node.nodeType == 3) // A text node
node.nodeValue = node.nodeValue.toUpperCase();
}
traverseDocument(document.body, capitaliseText);
Это может быть преобразовано следующим образом: Мы добавляем дополнительный аргумент к каждой функции, который будет использоваться для передачи продолжения функции. Это продолжение представляет собой значение функции, представляющее действия, которые должны произойти после того, как функция «вернется». Стек (call) становится устаревшим в стиле передачи продолжения - когда функция вызывает другую функцию, это последнее, что она делает. Вместо того, чтобы ждать возврата вызываемой функции, он помещает любую работу, которую он хочет сделать впоследствии, в продолжение, которое он передает функции.
function traverseDocument(node, func, c) {
var children = node.childNodes;
function handleChildren(i, c) {
if (i < children.length)
traverseDocument(children[i], func,
function(){handleChildren(i + 1, c);});
else
c();
}
return func(node, function(){handleChildren(0, c);});
}
function capitaliseText(node, c) {
if (node.nodeType == 3)
node.nodeValue = node.nodeValue.toUpperCase();
c();
}
traverseDocument(document.body, capitaliseText, function(){});
Представьте, что у нас есть огромный документ, который нужно использовать заглавными буквами. Простое его прохождение занимает пять секунд, а зависание браузера на пять секунд - довольно плохой стиль. Рассмотрим простую модификацию capitaliseText (не обращайте внимания на уродливый глобал):
var nodeCounter = 0;
function capitaliseText(node, c) {
if (node.nodeType == 3)
node.nodeValue = node.nodeValue.toUpperCase();
nodeCounter++;
if (nodeCounter % 20 == 0)
setTimeout(c, 100);
else
c();
}
Теперь, каждые двадцать узлов вычисление прерывается на сто миллисекунд, чтобы дать интерфейсу браузера момент для ответа на ввод пользователя. Очень примитивная форма многопоточности - вы даже можете запускать несколько вычислений одновременно, как это.
Более полезное применение этого связано с XMLHttpRequests или различными хаками IFRAME и SCRIPT, используемыми для их моделирования. Это всегда требует, чтобы один работал с каким-то механизмом обратного вызова для обработки данных, которые сервер отправляет обратно. В простых случаях подойдет тривиальная функция, или можно использовать несколько глобальных переменных для хранения состояния вычислений, которое должно быть возобновлено после возвращения данных. В сложных случаях, например, когда данные используются функцией, которая должна возвращать вызывающей стороне некоторое значение, продолжения значительно упрощают ситуацию. Вы просто регистрируете продолжение как обратный вызов, и после завершения запроса ваши вычисления возобновляются.