Как правильно генерировать исключения в PHP/Dojo при возврате кода javascript
Мне трудно понять, как обрабатываются исключения, когда динамически динамически вводится код через AJAX и выполняется через eval
. С клиентским javascript это довольно просто, если у меня есть код, такой как
var j = 'some string';
j.propA.x++;
это вызовет исключение, поскольку propA
, который имеет тип undefined
, не имеет x
. Кроме того, возникшее исключение очень легко понять.
Теперь давайте разместим вышеуказанный код в текстовом файле, позвоним ему test.js
и сохраним его на сервере. Теперь давайте загрузим его динамически с помощью Ajax. Я использую следующий код для динамического его загрузки
dojo.xhrGet({
url: 'load.php',
handleAs: "javascript",
content : {
fileName : 'test.js'
},
load: function(returnValue) {
/*Do Something*/
},
error: function(errorMessage) {
/*Report Error*/
}
});
Вот очень простой php script для загрузки файла и возврата его в виде кода javascript
<?php
$fileName = $_GET['fileName'];
$handle = fopen($fileName , 'r');
$script = fread($handle, filesize($fileName));
fclose($handle);
echo $script;
?>
В приведенном выше вызове dojo.xhrGet
свойство error
может быть настроено на функцию, отображающую сообщение об ошибке, вот пример некоторых из многих способов, которые это можно сделать.
error: function(errorMessage) {
console.error(errorMessage);
console.error(errorMessage.arguments);
console.error(errorMessage.message);
console.error(errorMessage.stack);
console.error(errorMessage.type);
}
Ниже приведен пример вывода. Хотя этот вывод для другой проблемы, он подчеркивает, насколько непостижимо это:
Cannot read property 'x' of undefined
TypeError: Cannot read property 'x' of undefined
at eval at <anonymous> (http://o.aolcdn.com/dojo/1.6/dojo/dojo.xd.js:14:3088)
at Object.load (http://192.168.1.8/easel.js:166:6)
at http://o.aolcdn.com/dojo/1.6/dojo/dojo.xd.js:14:89998
at _144 (http://o.aolcdn.com/dojo/1.6/dojo/dojo.xd.js:14:36518)
at _142 (http://o.aolcdn.com/dojo/1.6/dojo/dojo.xd.js:14:36328)
at [object Object].<anonymous> (http://o.aolcdn.com/dojo/1.6/dojo/dojo.xd.js:14:36994)
at _144 (http://o.aolcdn.com/dojo/1.6/dojo/dojo.xd.js:14:36780)
at _142 (http://o.aolcdn.com/dojo/1.6/dojo/dojo.xd.js:14:36328)
at [object Object].<anonymous> (http://o.aolcdn.com/dojo/1.6/dojo/dojo.xd.js:14:36994)
at Object.resHandle (http://o.aolcdn.com/dojo/1.6/dojo/dojo.xd.js:14:92730)
non_object_property_load
Я предполагаю, что dojo.xd.js:14
- это строка, в которой выполняется оператор eval
.
Если кто-то знает, что они ищут, этого может быть достаточно. Однако существует ли более простой или, по крайней мере, более эффективный способ устранения исключений, возникающих в eval
?
Здесь - несколько схожий вопрос.
Phikin предоставил хорошее решение для этой проблемы ниже, поэтому я дал ему щедрость. Используя его решение, я получил вывод, который выглядел примерно так (я немного его сократил)
ReferenceError in JS Code detected: (url: module.require.php?module=MainMenu.Bg_S)
easel.js:211Error Message: ReferenceError: apple is not defined
easel.js:213(function(){
return function(args){
dojo.require("Shape");
Module.assert('MainMenu_V');
/**
* The rectangular background of the Main View
* @property MainMenuBg_S
* @type Shape
**/
new Shape({
/**
* Unique descriptive name used when later accessing this shape via '$$()'
* @param name
* @type String
**/
name : 'MainMenu.Bg_S' ,
/**
* Left side of this rectangle
* @param x
* @type Number
**/
x : $$('MainMenu_V').x ,
/**
* Top of this rectangle
* @param y
* @type Number
**/
y : $$('MainMenu_V').y ,
/**
* Width of this rectangle
* @param w
* @type Number
**/
w : $$('MainMenu_V').w ,
/**
* Height of this rectangle
* @param h
* @type Number
**/
h : $$('MainMenu_V').h ,
/**
* Type of this Shape
* @param h
* @type Number
**/
type : shapeType.RECTANGLE ,
/**
* Generate function which contains all the graphics instructions, as well as the contexts
* to preload and initialize. This is currently under development. Backgrounds should NEVER
* have mouse events associated with them as a redraw of a background implies a redraw of
* every single displayObject infront of the background.
* @param generate
* @type method
**/
generate : function (){
var x = this.x << 0 , y = this.y << 0 , h = this.h << 0 , w = this.w << 0 , a = this.a;
this.graphics(contextID.LEAVE).lf([hsl(180,100,60,0.9),hsl(180,100,20,0.75)],[0,1],0,h/2,w,h/2).dr(x,y,w,h).ef();
this.graphics(contextID.ENTER).lf([hsl(135,100,40,0.9),hsl(135,100,20,0.75)],[0,1],0,h/2,w,h/2).dr(x,y,w,h).ef();
this.graphics(contextID.CLICK).lf([hsl(90,100,40,0.9),hsl(90,50,20,0.75)],[0,1],0,h/2,w,h/2).dr(x,y,w,h).ef();
this.graphics(contextID.RCLICK).lf([hsl(90,110,40,0.9),hsl(80,60,20,0.45)],[0,1],0,h/2,w,h/2).dr(x,y,w,h).ef();
this.graphics(contextID.DBLCLICK).lf([hsl(45,100,40,0.9),hsl(45,100,20,0.75)],[0,1],0,h/2,w,h/2).dr(x,y,w,h).ef();
this.graphics(contextID.DBLRCLICK).lf([hsl(10,100,40,0.9),hsl(10,100,20,0.75)],[0,1],0,h/2,w,h/2).dr(x,y,w,h).ef();
this.graphics(contextID.LPRESS).lf([hsl(110,25,40,0.9),hsl(110,25,20,0.75)],[0,1],0,h/2,w,h/2).dr(x,y,w,h).ef();
this.graphics(contextID.RPRESS).lf([hsl(110,50,40,0.9),hsl(110,50,20,0.75)],[0,1],0,h/2,w,h/2).dr(x,y,w,h).ef();
this.graphics(contextID.SCROLL).lf([hsl(110,50,40,0.9),hsl(110,50,20,0.75)],[0,1],0,h/2,w,h/2).dr(x,y,w,h).ef();
if (debugFlags.BOUNDINGBOX()){
this.graphics(contextID.ENTER).ss(2).s(rgba(0,255,0,a)).dr(this.boundingBox.softBounds.L +4<<0, this.boundingBox.softBounds.T +4<<0, this.boundingBox.softBounds.w-8<<0 , this.boundingBox.softBounds.h-8<<0).es();
this.graphics(contextID.ENTER).ss(2).s(rgba(255,0,0,a)).dr(this.boundingBox.bounds.L +4<<0, this.boundingBox.bounds.T +4<<0, this.boundingBox.bounds.w-8<<0 , this.boundingBox.bounds.h-8<<0).es();
this.graphics(contextID.ENTER).f(rgba(0,0,255,a)).dc(this.boundingBox.points[0].x+4 , this.boundingBox.points[0].y+4 , 4).ef();
this.graphics(contextID.ENTER).f(rgba(0,0,255,a)).dc(this.boundingBox.points[1].x-8 , this.boundingBox.points[1].y+4 , 4).ef();
this.graphics(contextID.ENTER).f(rgba(0,0,255,a)).dc(this.boundingBox.points[2].x-8 , this.boundingBox.points[2].y-8 , 4).ef();
this.graphics(contextID.ENTER).f(rgba(0,0,255,a)).dc(this.boundingBox.points[3].x+4 , this.boundingBox.points[3].y-8 , 4).ef();
}
},
/**
* Arguments to pass to the mouse initialization function. These will get mixed in (via
* dojo.mixin) to the mouse object. To increase performance, the signalOrderIn has been set to
* NOHIT. This will limit the number of redraws (remember background redraws are extremely
* expensive as they require redrawing everything in the container). The signalOrderOut is
* then set to BLOCK to prvent anything behind the background from receiving mouse signals
* (this is actually unecessary as the only think behind the background is, and always should
* be, the container, which itself has signalOrderIn and signalOrderOut set to NOHIT and BLOCK
* respectively).
* @param mouse
* @type Object
**/
mouse : {
_signalOrderIN : signalFlags.NOHIT ,
_signalOrderOUT : signalFlags.BLOCK
} ,
/**
* All views are initially loaded via Ajax. Generally, views do not have any preconditions, beyond
* that the stage be present. They can, however, and generally do, have modules they require. These
* are called after this view has been created and loaded (load() function call). They are called
* in the order of the sub arrays. In the example below:
* [[A , B , C , D , E , F , G]]
* The 7 modules are requested in that order, but, due to Ajax, they can be loaded in any order.
* In the below example, on the other hand:
* [[A] , [B , C , D , E , F , G]]
* Modules B-G depend on module A, therefore, module A is ordered to be loaded first.
* @property providedModules
* @type Array[Array[String]]
* @protected
**/
providedModules : [[]] ,
/**
* Carries out all the initializations when loading the module
* @method load
* @protected
**/
load : function (){
0/apple;
$$('MainMenu_V').addChild(this);
} ,
/**
* Carries out all memory deallocation when leaving the module (generally only necessary if modules
* were loaded but not added to stage as in the case with cached bitmaps)
* @method leave
* @protected
**/
leave : function (){
}
});
$$('MainMenu.Bg_S')._code="dojo.require(\"Shape\");...";
};
}());
easel.js:217Error triggered by: function (_2bd){return err.call(args,_2bd,_2b7);}
easel.js:220XHR Object:
easel.js:221
Object
args: Object
handleAs: "javascript"
query: null
url: "module.require.php?module=MainMenu.Bg_S"
xhr: XMLHttpRequest
__proto__: Object
easel.js:222Error Object:
easel.js:223
ReferenceError
arguments: Array[1]
message: "—"
stack: "—"
type: "not_defined"
__proto__: Error
dojo.xd.js:14
ReferenceError
arguments: Array[1]
message: "—"
stack: "—"
type: "not_defined"
__proto__: Error
dojo.xd.js:14
ReferenceError
arguments: Array[1]
message: "—"
stack: "—"
type: "not_defined"
__proto__: Error
Единственное, что ему не хватает, что мне нужно, это возможность указать, в какой строке возникла проблема.
Ответы
Ответ 1
Ниже приведен фрагмент, который обнаруживает ошибки, не связанные с сетью, из запроса xhr-get и выводит некоторую информацию об этом в консоли.
Есть дополнительная функция isEvalError(), которая проходит через все типы ошибок eval... которые я не очень горжусь. Лучше всего было бы получить родительский объект подклассов errorMessage.
Я думаю, что вы можете вырезать isEvalError() вообще, потому что в этом блоке не должно быть никакой другой ошибки.
function isEvalError(errorMessage){
return errorMessage.name == "RangeError" ||
errorMessage.name == "ReferenceError" ||
errorMessage.name == "SyntaxError" ||
errorMessage.name == "URIError" ||
errorMessage.name == "TypeError";
}
var foo = dojo.xhrGet({
url: 'stacko.js',
handleAs: "javascript",
load: function(returnValue) {
console.log("load: "+returnValue);
},
error: function(errorMessage,ioargs) {
//request worked fine, this must be a non-network related error
if(ioargs.xhr.readyState == 4 && ioargs.xhr.status == 200) {
if(isEvalError(errorMessage)){
//show eval-error, url request & the JS code that causes the exception
//eval-error types: RangeError,ReferenceError,SyntaxError, URIError, TypeError
console.error(errorMessage.name+" in JS Code detected: (url: "+ioargs.url+")")
console.error("Error Message: "+ errorMessage);
console.error(ioargs.xhr.responseText);
}
//a little reflection - if u want to know who triggered this error
//(although in this case the output is not very helpful )
console.error("Error triggered by: "+arguments.callee.caller.toString());
//last but not least log the error & the xhr-request object for more information
console.error("XHR Object:");
console.error(ioargs);
console.error("Error Object:");
console.error(errorMessage);
}
}
});
Ответ 2
Это действительно зависит от того, что вы имеете в виду "продуктивный способ справиться с исключениями". Если вам нужно всего лишь просмотреть содержимое исключения, просто
console.log(errorMessage);
позволит вам легко проверить объект ошибки в приличном браузере, таком как Chrome или Firefox (с Firebug). (Вместо того, чтобы заставлять вас делать тонну команд console.log)
Досадная вещь о Dojo исключениях внутри асинхронного кода заключается в том, что они всегда пойманы и обрабатываются, поэтому большинство отладчиков браузера игнорируют их. Заметным исключением из этого правила является Chrome, где вы можете сказать отладчику приостановить все исключения.
BTW: Я не вижу, как в этом случае Javascript-исключения и Dojo имеют какое-то отношение к PHP, поскольку они происходят на стороне клиента, и сервер ничего не может с ними поделать. Кроме того, что, черт возьми, вы делаете, отправив код Javascript в AJAX? Большую часть времени клиент выполняет запрос, который будет использоваться для данных, в виде обычного текста, JSON или XML.