понедельник, 19 января 2015 г.

Как обойти сообщение Stop running this script

Если JavaScript-сценарий выполняется слишком долго (как правило это происходит в результате обработки огромного количества данных внутри цикла), то браузер выводит сообщение с предложением остановить работу сценария. Такое сообщение Internet Explorer выводит, к примеру, когда число синхронно выполняемых инструкций достигает максимума. (Установленное по умолчанию значение в размере 5 000 000 инструкций можно изменить в реестре Windows.) Этот механизм позволяет пользователю остановить сценарий, который выполняет бесконечный цикл или просто работает медленно.



Unresponsive Script

На выполнение сценария разные браузеры накладывают разные ограничения.

Internet Explorer ставит ограничение в 5 миллионов инструкций.
Firefox ограничивает время выполнения сценария 10 секундами.
Safari ограничивает время выполнения сценария 10 секундами.
Chrome не ограничивает время выполнения сценария, но определяет, когда браузер падает или становится неработоспособным.
Opera никак не ограничивает время выполнения сценария.

Чтобы изменить ограничение таймаута в Internet Explorer версии 4.0 до 8 выполните следующие действия.
С помощью редактора реестра, например Regedt32.exe, откройте thiskey:
HKEY_CURRENT_USER\Software\Microsoft\Internet Explorer\Styles
Создайте новое значение DWORD с именем MaxScriptStatements равным 0xFFFFFFFF во избежание появления диалогового окна.
По умолчанию данный ключ не существует. Если ключ не был добавлен, то предельное времени ожидания по умолчанию равно 5 000 000 инструкций для обозревателя Internet Explorer 4 и более поздних версий.

Для того, чтобы обойти ограничение на время выполнения сценария во всех браузерах, рекомендуется избегать рекурсий, заменяя их циклом while и разбивать долго выполняющийся циклы на части следующим образом.

var i = 0;
(function longCycle () {
    for (; i < 6000000; i++) {
        /*
            Основное тело цикла
        */

        // Каждые 100 000 итераций мы делаем принудительное прерывание цикла
        if (i > 0 && i % 100000 === 0) {
            // Вручную увеличиваем значение "i", так как мы делаем "break"
            i++;
            // Уставаливаем таймер для начала следующей итерации основного цикла
            window.setTimeout(longCycle);
            break;
        }
    }
})();

function RepeatingOperation (operation, yieldEveryIteration) {
    var count = 0, instance = this;
    this.step = function (args) {
        if (++count >= yieldEveryIteration) {
            count = 0;
            setTimeout(function(){operation(args);}, 1, []);
            return;
        }
        operation(args);
    };
};


var test = new Array(initdata.length)
   , i = 0
   , ro = new RepeatingOperation(function(){
         test2[i] = initdata[i] * 2;
          if (++i < initdata.length) {
              ro.step();
          } else {
              continueOperations();
          }
      }, 100);

ro.step();

пятница, 16 января 2015 г.

Про Монады

После прочтения десятков «самых понятных введений в монады» и чтения (тоже) десятков обсуждений на разных форумах я пришёл к выводу, что существует группа абстрактных ОО-программистов, которым моя интерпретация «чего-то похожего на монады» может помочь немного приблизиться к правильному пониманию.

Итак, в этой публикации вы не найдете ответы на следующие вопросы:
1. Что такое монада?
2. Где и как использовать монады?
3. Почему монады лучше, чем их отсутствие?

В программировании есть такой феномен — «паттерны проектирования». Официально это набор лучших практик, которыми следует руководствоваться при решении «типичных задач». Неофициально — просто набор костылей для языков, в которых нет встроенных средств для решения типичных проблем.

Есть такой паттерн проектирования — Interpreter. Замечателен он в первую очередь тем, что позволяет сделать некое подобие виртуальной машины поверх любимого языка программирования, при этом:

1. Можно описать программу на языке, понятном виртуальной машине.
2. Можно грабить корованы во всех подробностях описать как виртуальная машина должна интерпретировать каждую инструкцию.

Всё что написано ниже имеет смысл только если любезный читатель минимально знаком с упомянутым паттерном.

Сравнительно каноничный пример:
function add(x) {
  return { op: "add", x: x };
}

function div(x) {
  return { op: "div", x: x };
}

function run(value, statements) {
  for(var i = 0; i < statements.length; ++i) {
    var statement = statements[i];
    var op = statement.op;
    var x = statement.x;
    if(op === "add") {
      value += x;
    } else if(op === "div") {
      value /= x;
    } else {
      throw new Error("Unknown operation " + op);
    }
  }
  return value;
}

var program = [
  add(10),
  div(3)
];

var result = run(0, program);
console.log(result); // 3.3333...

Любители GoF могут поспорить, мол, «это Command, а не Interpreter». Для них пусть это будет Command. В контексте статьи это не очень важно.

В этом примере, во-первых, есть программа, состоящая из двух инструкций: «добавить 10» и «разделить на 3». Что бы это ни значило. Во-вторых, есть исполнитель, который делает что-то осмысленное глядя на программу. Важно заметить, что «программа» влияет на результат своего исполнения очень косвенно: исполнитель совершенно не обязан выполнять инструкции сверху-вниз, он не обязан выполнять каждую инструкцию ровно 1 раз, он вообще может вызовы add() транслировать в «Hello», а div() — в «World».

Договоримся, что трансляция add() в console.log() нам неинтересна. Интересны вычисления. Поэтому немного упростим код, отказавшись от ненужной гибкости:

function add(x) { // add(2)(3) === 5
  return function(a) { return a + x; };
}

function div(x) { // div(10)(5) === 2
  return function(a) { return a / x; };
}

function run(value, statements) {
  for(var i = 0; i < statements.length; ++i) {
    var statement = statements[i];
    value = statement(value);
  }
  return value;
}

var program = [ add(10), div(3) ];
var result = run(program);
console.log(0, result); // 3.3333...

Здесь стоит остановиться. У нас есть некий инструмент, позволяющий отдельно описывать программу и отдельно «способ её исполнения». В зависимости от наших пожеланий к результату исполнения, реализация исполнителя может быть очень разной.

Например, хочется, чтобы как только где-то в вычислениях появляется NaN,null или undefined, вычисления прекращались и возвращался результат null:

...
function run(value, statements) {
  if(!value) {
    return null;
  }
  
  for(var i = 0; i < statements.length; ++i) {
    var statement = statements[i];
    value = statement(value);
    if(!value) {
      return null;
    }
  }
  
  return value;
}

console.log(run(undefined, [add(1)])); // null
console.log(run(1, [add(undefined)])); // null

Хорошо. А что если мы хотим одну и ту же программу выполнять для коллекции разных начальных значений? Тоже не вопрос:

...
function run(values, statements) {
  return values.map(function(value) {
    for(var i = 0; i < statements.length; ++i) {
      var statement = statements[i];
      value = statement(value);
    }
    return value;
  });
}

var program = [ add(10), div(3) ];
console.log(run([0, 1, 2], program)); // [3.333..., 3.666..., 4]

Здесь снова стоит остановиться. Мы используем одни и те же выражения, чтобы описывать программу, но в зависимости от исполнителя получаем очень разные результаты. Попробуем теперь снова немного переписать пример. В этот раз, во-первых, уберём ещё немного гибкости: выражения теперь выполняются строго от первых — к последним, а во-вторых, избавимся от цикла внутри run(). Результат назовём словом Context (чтобы никто не догадался):

...
function Context(value) {
  this.value = value;
}

Context.prototype.run = function(f) {
  var result = f(this.value);
  return new Context(result);
};

var result = new Context(0)
  .run(add(10))
  .run(div(3))
  .value;

console.log(result); // 3.3333... 

Реализация сильно отличается от предыдущих вариантов, но делает оно примерно то же самое. Здесь предлагается ввести термин мунада (от англ.moonad — «лунная реклама»). Здравствуй, Identity moonad:

...
function IdentityMoonad(value) {
  this.value = value;
}

IdentityMoonad.prototype.bbind = function(f) {
  var result = f(this.value);
  return new IdentityMoonad(result);
};

var result = new IdentityMoonad(0)
  .bbind(add(10))
  .bbind(div(3))
  .value;

console.log(result); // 3.3333... 

Эта штука чем-то отдалённо похожа на Identity monad.

Вспомним теперь про тот вариант исполнителя, где мы боролись с NaN и попробуем переписать его используя новый подход к реализации:

function MaybeMoonad(value) {
  this.value = value;
}

MaybeMoonad.prototype.bbind = function(f) {
  if(!this.value) {
    return this;
  }
  
  var result = f(this.value);
  return new MaybeMoonad(result);
};

var result = new MaybeMoonad(0)
  .bbind(add(10))
  .bbind(add(undefined))
  .bbind(div(3))
  .value;

console.log(result); // null

Можно даже более привычный пример:

var person = {
  // address: {
  //   city: {
  //     name: "New York"
  //   }
  // }
};

console.log(person.address.city.name); // падает

console.log(new MaybeMoonad(person)
  .bbind(function(person) { return person.address; })
  .bbind(function(address) { return address.city; })
  .bbind(function(city) { return city.name; })
  .bbind(function(cityName) { return cityName; })
  .value); // не падает, возвращает null

Издалека может показаться, что это Maybe monad. Любезному читателю предлагается самостоятельно реализовать что-то похожее на List monad.

При базовых навыках работы напильником не составит изменитьIdentityMoonad таким образом, чтобы вызовы f() стали асинхронными. В результате получится Promise moonad (что-то похожее на q).

Теперь, если внимательно приглядеться к последним примерам, можно попробовать дать более-менее формальное определение мунады. Мунада — это штука, у которой есть 2 операции:
1. return — принимает обычное значение, помещает его в мунадический контекст и возвращает этот самый контекст. Это просто вызов конструктора.
2. bind — принимает функцию от обычного значения, возвращающую обычное значение, выполняет её в контексте мунадического контекста и возвращает монадический контекст. Это вызов `bbind()`

понедельник, 12 января 2015 г.

Восстановление значения по умолчанию после удаления свойства объекта.

Восстановление значения по умолчанию после удаления свойства объекта можно реализовать через prototype.

function BaseObject  (name) {
    if (typeof name !== "undefined") {
        this.name = name;
    } else {
        this.name = 'default'
    }
}

var firstObj = new BaseObject();
var secondObj = new BaseObject('unique');

console.log(firstObj.name);  // -> в 'default'
console.log(secondObj.name); // -> в 'unique'

// Удалим свойство объекта
delete secondObj.name;

// В этом случае получим:
console.log(secondObj.name); // -> в 'undefined'

// А, если записать через prototype

function BaseObject (name) {
    if (typeof name !== "undefined") {
        this.name = name;
    }
};

BaseObject.prototype.name = 'default';

var thirdObj = new BaseObject('unique');
console.log(thirdObj.name);  // -> в 'unique'

// Удалим свойство объекта
delete thirdObj.name;

// Теперь свойство объекта имеет значение по умолчанию
console.log(thirdObj.name);  // -> в 'default'