Статьи

JavaScript для приложений Магазина Windows: обработка ошибок

Вы уверены, что пишете свой код JavaScript без ошибок? Вам интересно, применяются ли традиционные методы обработки ошибок JavaScript при разработке приложений для Магазина Windows? Предоставляет ли библиотека Windows для JavaScript дополнительную поддержку для обработки ошибок? Вы по-разному реагируете на ошибки при написании асинхронного кода? Весь этот пост посвящен решению этих вопросов!

Человеку свойственно ошибаться

Давайте будем ясны: если бы вы были идеальны и могли писать код, который никогда не выходит из строя, вам не нужно было бы читать дальше. Для остальных из нас (то есть для всех нас) мы должны признать, что ошибки очень часто проникают в код. Кроме того, при разработке с языком, который не является строго типизированным, таким как JavaScript, существует вероятность еще большего количества ошибок. Зачем? Рассмотрим следующий код:

var x = 4;

// somewhere else in code...
x = "four";

Код выше действителен. Это демонстрирует динамическую природу языка. Тем не менее, это также иллюстрирует потенциальную путаницу относительно того, что переменная х . Любое неправильное использование x на основе допущений может привести к ошибочному поведению. Один из способов свести к минимуму проблемы такого рода — это согласовывать с переменными присваивания в отношении предполагаемого типа. Другими словами, если переменная назначена строке, оставьте ее строкой.

Синтаксические ошибки обычно сообщаются через Visual Studio при запуске приложения в режиме отладки. Однако в сообщении об ошибке может быть неясно указано, что необходимо для исправления. Например, соблюдайте следующую синтаксическую ошибку:

varr x = 4;

Изолированный от всех других строк кода, легко увидеть, что ключевое слово var было написано с ошибкой. Однако IDE сообщает об ошибке во время выполнения следующим образом:

критическая ошибка JavaScript

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

Для веб-разработчиков, которые переносят свои навыки JavaScript в разработку приложений для Магазина Windows, использование операторов try… catch… finally полностью поддерживается и должно использоваться при попытке выполнить любую строку кода, которая является условием успешного результата, например доступ к внешним ресурсам.

Недоверие это добродетель

Я нахожу немного смешным, что неинициализированное использование WinJS.log (метод для регистрации информации об ошибках среди прочего) вызовет ошибку во время выполнения. Метод log можно инициализировать с помощью WinJS.Utilities.startLog (для входа в консоль JavaScript) или назначить пользовательскому методу с режимами ведения журнала, определенными разработчиком. Прямой вызов метода log без инициализации приведет к следующему:

Ошибка времени выполнения JavaScript: объект не поддерживает свойство или метод ‘log’

Конечно, когда метод журнала инициализирован — об ошибках не сообщается. Однако рассмотрим сценарий, в котором параметры приложения определяют, будет ли инициализирован метод журнала. В таком случае, что является безопасным способом вызова метода журнала? Некоторые могут создать глобальную переменную, которую можно использовать в операторе if, чтобы увидеть, включено ли ведение журнала перед каждым вызовом. Это будет работать, но есть другой способ. Рассмотрим следующий код:

WinJS.log("Risky", "demo", "info");
WinJS.log && WinJS.log("Safer", "demo", "info");

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

Есть еще один метод, использующий || оператор, который может помочь уменьшить количество ошибок, создав значение по умолчанию, если оно не указано. В следующем коде параметру name присваивается значение, если ни один из них не был уже назначен:

function echoName(name) {
    name = name || "John Doe";
    return name.toUpperCase();
}

Приведенный выше код даст следующий вывод:

echoName();             // JOHN DOE
echoName("M Palermo");  // M PALERMO

Пока все хорошо, но что делать, если сделан следующий вызов:

echoName(4);

Передача нестрокового значения вызывает следующую ошибку:

Ошибка времени выполнения JavaScript: объект не поддерживает свойство или метод toUpperCase

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

if (name && name.toUpperCase) {
    // safely call name.toUpperCase();
}

В других случаях может быть предпочтительнее просто подтвердить, какой это тип. Распространенный способ сделать это — использовать оператор typeof, который возвращает строку, сообщающую, что является базовым типом, как показано здесь:

var x;
(typeof x === 'undefined') // true

x = 4;
(typeof x === 'number')    // true

В то время как typeof работает как положено в большинстве случаев, в других случаях он может разрешать типы, отличные от наших, как показано здесь:

(typeof 'abc' === 'string')             // true
(typeof new String("abc") === 'string') // false – type is object

Чтобы определить фактическое значение типа, рассмотрите реализацию вспомогательной функции, подобной той, что продемонстрирована здесь:

function isTypeOf(o,t) {
    if (t && t.charAt && t.charAt(0)) {
        return (Object.prototype.toString.call(o) === "[object " + t + "]");
    } else {
        throw new WinJS.ErrorFromName("args", 
            "'t' required and must be a string.");
    }
}


isTypeOf("abc", "String");             // true
isTypeOf(new String("abc"), "String"); // true
isTypeOf(4);                           // error thrown

Функция isTypeOf сначала проверяет, есть ли у параметра t значение, что у него есть метод charAt и что вызов charAt (0) не вернет пустую строку. Если все условия выполняются, функция возвращает значение на основе сравнения t и типа, представленного в Object . прототип . toString . звоните . Обратите внимание, что если параметр t не указан или не является строкой, выдается ошибка с использованием фабричного метода WinJS.ErrorFromName, который является простым способом создания объекта ошибки на основе имени (типа ошибки или категории) и сообщения.

Так зачем выкидывать ошибку? Разве это не противоречит цели предотвращения проблем? В приведенном выше примере ошибка генерируется из вспомогательной функции. Поскольку эта функция может оказаться в многократно используемой библиотеке, это методология передачи информации о ее неправильном использовании в код уровня приложения. При таком понимании вы, скорее всего, будете тратить гораздо больше времени на обработку ошибок в коде уровня приложения, чем на их создание.

Главное сообщение до этого момента — делать упреждающие удары в вашем коде, чтобы минимизировать как можно больше ошибок. А как насчет ошибок, которые остались необработанными?

Ничего не оставляйте без присмотра

Для всех неизвестных ошибок, которые могут произойти в вашем приложении, существует простой способ поймать их всех в одном месте. У объекта WinJS.Application есть событие onerror, которое перехватывает все необработанные ошибки в одном месте. Поскольку ошибка может произойти в любое время, ее следует подключить к обработчику событий как можно скорее в течение времени жизни приложения. Большинство шаблонов проектов JavaScript для Магазина Windows содержат файл default.js, в котором происходит инициализация приложения. В верхней части этого файла вы можете добавить прослушиватель событий для необработанных ошибок приложения, как показано здесь:

(function () {
    "use strict";
    
    WinJS.Binding.optimizeBindingReferences = true;

    var app = WinJS.Application;
    var activation = Windows.ApplicationModel.Activation;
    var nav = WinJS.Navigation;

    app.addEventListener("error", function (err) {
        var md = Windows.UI.Popups.MessageDialog;
        var msg = new md(err.detail.message, "Ooops!");
        msg.showAsync();
        // return true; // only if error is handled
    });

    [remaining code...]

})();

Если в приложении возникает необработанная ошибка, вызывается вышеприведенная функция слушателя. Сообщение отображается для пользователя , указывающего подробности того , что произошло, а затем приложение останавливается. Приложение закрывается, потому что ошибка все еще считается необработанной. Возвращение true из обработчика ошибок сообщает системе, что ошибка была обработана и можно продолжить.

Необработанные ошибки также могут быть обнаружены на уровне страницы во время обработки страницы. Это особенно легко сделать при использовании WinJS.UI.Pages . Следующий код является примером того, как добавить прослушиватель события ошибки для экземпляра PageControl в методе define :

WinJS.UI.Pages.define("/pages/errors/errors.html", {
 
    error: function (err) {
        WinJS.log && WinJS.log(err.message, "demo", "error");
    },

    [remaining code...]

});

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

Когда обещания нарушаются

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

function doAsync(msg, limit) {
    return new WinJS.Promise(
        // c complete, e error, p progress
        function (c, e, p) {
        msg = msg || "no-msg";
        limit = limit || 3;
        var seconds = 0; 
        var iId = window.setInterval(function () {
            try {
                seconds++;
                if (msg === "fail") {
                    throw new WinJS.ErrorFromName(
                        "promise", "Muhahaha!");
                }
                p && p(msg + ": " + seconds.toString());
                if (seconds > limit) {
                    window.clearInterval(iId);
                    c && c(msg);
                }
            } catch (ex) {
                window.clearInterval(iId);
                e && e(ex);
            }

        }, 1000); // repeat every second, stop at limit
    });
}

Определенная выше функция doAsync — это пользовательское обещание. При вызове позволяет использовать сообщение (переданное в функцию через параметр msg ) через несколько секунд (определяемых параметром limit ). Когда достигается ограничение по времени, вызывается «полная» функция (представленная параметром c ). Функция «прогресс» (представленная параметром p ) вызывается каждую секунду, что позволяет приложению потенциально обновлять пользовательский интерфейс. И если возникают какие-либо ошибки, блок catch передает ошибку в функцию «error» (представленную параметром e ). Если значение msg равно «fail», выдается ошибка.

Следующий код демонстрирует все вспомогательные методы, которые будут использоваться при вызове функции doAsync :

function doComplete(msg) {
    WinJS.log && WinJS.log(msg + ": complete", "demo", "info");
}

function doProgress(msg) {
    WinJS.log && WinJS.log(msg, "demo", "info");
}

function doError(err) {
    var msg = "undefined error";
    if (err) {
        msg = err.detail;
        if (!(msg && msg.message))
            msg = err.message || "err type unknown";
    }
    WinJS.log && WinJS.log(msg, "demo", "error");
}

Каждая функция вызывает WinJS.log   для отображения данных. Теперь вот код, который объединяет их всех:

doAsync("promise", 4) // doError not called
    .then(doComplete, doError, doProgress);
    // final call was to doComplete function

doAsync("fail", 4) // doComplete not called
    .then(doComplete, doError, doProgress);
    // final call was to doError function

При каждом вызове doAsync, указанном выше, передается сообщение, и продолжительность асинхронного вызова будет ограничена 4 секундами. Каждая секундная отметка будет вызывать функцию doProgress . Если все идет хорошо, когда 4 секунды истекли , вызывается функция doComplete . Однако, если обещание обнаруживает ошибку, вместо этого вызывается функция doError .

Обещания могут быть объединены в цепочку, чтобы один асинхронный вызов мог вызывать следующий после завершения. При объединении в цепочку обещаний помещайте обработчик ошибок только в последнее звено цепочки — метод done . Вот пример объединения обещаний с использованием функции doAsync :

doAsync("chain(1)", 4)
    .then(function (msg) {
        doComplete(msg);
        return doAsync("fail", 4);
    }, null, doProgress)// null passed in for error
    .then(function (msg) {
        doComplete(msg);
        return doAsync("chain(2)");
    }, null, doProgress)// null passed in for error
    .done(null, doError);
    // doError only needed in done method

В приведенной выше цепочке обещаний, когда «chain (1)» завершается, она вызывает «fail». Поскольку «fail» приведет к выдаче ошибки, «chain (2)» никогда не вызывается, и обработка направляется в функцию doError, переданную в метод done  цепочки. 

Обещания также могут быть объединены вместе. Это позволяет одновременно выполнять несколько асинхронных вызовов, но позволяет вызывать метод после завершения всех связанных обещаний. При обработке ошибок в этом сценарии сделайте следующее:

var promises = [];
var i = 0;
// each promise has an error handler
promises[i++] = doAsync("2seconds", 2)
    .then(doComplete, doError);
promises[i++] = doAsync("fail", 8)
    .then(doComplete, doError);
promises[i++] = doAsync("3seconds", 3)
    .then(doComplete, doError);
// join promises
WinJS.Promise.join(promises)
    .done(function () {
        doProgress("all promises completed");
    }, doError);

В отличие от цепочки, объединенные обещания должны иметь свои собственные обработчики ошибок. Если в одном из обещаний произойдет сбой, это не остановит остальных в объединении .

На последнем замечании, касающемся обещаний, обработчик ошибок может быть присоединен к WinJS.Promise.onerror . Сделайте это в качестве защитной сетки в дополнение к методологиям, которые были рассмотрены до сих пор.

Конец изящно

Итак, помните эти полезные рекомендации и советы при написании приложения для Магазина Windows на JavaScript:

  • Ошибки случаются — даже в вашем коде.
  • Сообщения об ошибках могут быть расплывчатыми, поэтому изучите контекст подозрительного кода!
  • Полагать, что ваш код должен работать, — плохая идея.
  • Избегайте распространенных ошибок, внедряя хорошую стратегию проверки типов.
  • Обрабатывать необработанные на уровне приложения и уровне страницы.
  • Используйте обработчики ошибок при работе с обещаниями.

Если вы еще этого не сделали, убедитесь, что вы получаете все последние ресурсы, создав профиль в Generation App !