[英]transpiler battle: breaking out of nested function, with vs without throw
我剛剛完成了我的第一個(玩具)轉譯器的“版本 0”。 有用。 它將一串“偽 JavaScript”(帶有附加功能的 JavaScript)轉換為一串可運行的 JavaScript。 現在,我想改進它。
其他 SO 用戶可能最感興趣的工作區域是:編譯后的代碼(即,我的轉譯器的 output)沒有注意到前面一些 SO 問題的已接受答案中給出的編碼風格建議。 如果我手頭有第二個轉譯器,該編碼風格建議得到重視,我可以就哪個分支更有希望繼續開發做出明智的決定——我想比較這兩個分支的性能、開發時間需要,錯誤的數量等,並據此決定。
讓我告訴你我的轉譯器處理的“附加 JS 功能”:“嵌套返回”。 考慮像這樣的閉包/嵌套函數
function myOuterFunc(){
... code ...
function innerFunc(){
... code ...
}
... code ...
}
(請注意,上面的 '...code...' 應該包括所有可能的 JS 代碼,包括更多嵌套的 function 聲明,因此myOuterFunc
不一定是innerFunc
的直接父級)
在上述情況下,假設您希望從內部某處返回myOuterFunc
的結果 - 不一定直接在內部 - innerFunc
實現“嵌套返回”后,您可以簡單地編寫
return.myOuterFunc result
這是(不可運行的)function 使用此功能並做一些有意義的事情的示例
function multiDimensionalFind(isNeedle, haystack) {
// haystack is an array of arrays
// loop (recursively) through all ways of picking one element from each array in haystack
// feed the picked elements as array to isNeedle and return immediately when isNeedle gives true
// with those picked elements being the result, i.e. the 'found needle'
var LEVEL = haystack.length;
function inner(stack) {
var level = stack.length;
if (level >= LEVEL) {
if (isNeedle(stack)) return.multiDimensionalFind stack;
} else {
var arr = haystack[level];
for (var i = 0; i < arr.length; i++) {
inner(stack.concat([arr[i]]));
}
}
}
inner([]);
return 'not found'
}
這是我的轉譯器自動生成的(可運行的)代碼(很明顯,注釋是后來添加/刪除的),然后是一些代碼測試,如果 function 做了它聲稱做的事情(它確實如此,因為你可以說服自己。 )
///////////// the function ///////////////// function multiDimensionalFind(isNeedle, haystack) { try { var LEVEL = haystack.length; function inner(stack) { var level = stack.length; if (level >= LEVEL) { if (isNeedle(stack)) throw stack; } else { var arr = haystack[level]; for (var i = 0; i < arr.length; i++) { inner(stack.concat([arr[i]])); } } } inner([]); return 'not found' } catch(e){ // make sure "genuine" errors don't get destroyed or mishandled if (e instanceof Error) throw e; else return e; } } ////////////////// test it ////////////////// content = document.getElementById('content'); function log2console(){ var digits = [0,1]; var haystack = [digits,digits,digits,digits,digits]; var str = ''; function isNeedle(stack){ str = str + ', ' + stack.join('') return false; } multiDimensionalFind(isNeedle, haystack); content.textContent = str; } function find71529(){ // second button var digits = [0,1,2,3,4,5,6,7,8,9] var haystack = [digits,digits,digits,digits,digits] function isNeedle(stack){ return stack.reduce(function(b,i){ return 10*b+i; }, 0) === 71529 // returns true iff the stack contains [7,1,5,2,9] } content.textContent = multiDimensionalFind( isNeedle, haystack ).join('_') }
<button onclick='log2console()'>print binary numbers with 5 digits</button> <br> <button onclick='find71529()'>find something is 5d space</button> <div id='content'></div>
你可以在這里玩我的這個小提琴中的轉譯器。 我正在使用esprima 庫,即esprima之上的 escodegen.js 庫,這是我自己的一個正在進行中的微小的抽象語法樹生成庫(請參閱小提琴中的腳本標簽)。 不是庫的代碼,也不是 UI 代碼,即轉譯器的“真肉”不到 100 行(參見 function transpile
)。 所以這可能沒有你想象的那么復雜。
我不記得我在哪里看到過風格推薦,但我可以肯定它實際上在多個地方。 如果您知道或遇到這樣一個問題,我邀請您將鏈接放入問題下方的評論中,我將標記為有用。 到目前為止,只有一個鏈接,謝謝 Barmar。
你可能會問為什么我什至費心先寫一個“不兼容”的轉譯器,而沒有立即為“兼容”版本編寫 go。 這與估計的工作量有關。 我估計“合規”版本的工作量要大得多。 如此之多,以至於開始這樣的努力似乎並不值得。 我很想知道這種對工作量的評估是正確的還是錯誤的。 因此問題。 請不要暗示問題的修辭,甚至是不誠實的動機; 不管對某些人來說聽起來多么奇怪,我確實希望被證明是錯誤的,所以請不要以為我出於任何原因“只是這么說”,你會對我做一個不公正。 到目前為止,在我提出的所有問題中,這是迄今為止我投入最多的一個。 而且,如果你問我,這是迄今為止我在這里問過的最好的問題。
除了有人幫助我編寫轉譯器的“合規”版本之外,我還對任何客觀可證明的東西感興趣(盡管程度較低),這有機會說服我“不合規”的方式是錯誤的方法。 速度測試(帶有 jsperf 的鏈接)、可重現的錯誤報告,諸如此類。
我應該提到到目前為止我自己進行的速度測試:
更好的方法是使用return
(如果你不能完全重構塔)。 它清楚地說明了您在做什么以及何時從中間函數返回值; 您可以通過查看可能提供結果的那些函數來判斷。 相反,在那些中間層中使用throw
是不可見的; 它以一種為錯誤條件設計的方式神奇地繞過它們。
如果您不想這樣做,我認為您除了throw
之外沒有其他合理的選擇。 我想知道沿着生成器函數或類似函數的路線走下去,但我認為這只會使事情變得更加復雜,而不是更少。
使用return
不會使示例代碼明顯復雜化,並且(在我看來)確實使正在發生的事情以及何時可能預期結果更清楚。
function wrapper(){
function first(){
function second(){
function third(){
doStuff4();
some loop {
var result = ...
if (something) return result;
}
}
doStuff2();
let result = third();
if (result) {
return result;
}
doStuff3();
return third();
}
doStuff1();
return second();
}
return first() || "not found";
}
(在上面, result
是真實性的;如果合適的話,用其他東西代替。)
好的,這是使用 JavaScript 的所有異步功能的另一種方法......所以基本上我已經重新創建了您的嵌套函數,但使用了 Promise/await 技術。 您只會得到一次結果,這是您第一次從任何級別的嵌套函數中解析它。 其他一切都將是GC。 一探究竟:
// Create async function (async () => { const fnStack = (val) => { return new Promise((resolve, reject) => { // Worker functions. // Could be async; const doStuff1 = (val) => val + 1; const doStuff2 = (val) => val * 2; const doStuff3 = (val) => val * -1; // This will not affect result const doStuff4 = (val) => val + 1000; // Nested hell function first() { function second() { function third() { val = doStuff4(val); // Some loop for(let i = 0; i < 1000; i++) { if(i === 500) { // Here we got our result // Resolve it; resolve(val); } } } val = doStuff2(val); third(); // Below code will not affect // resolved result in third() above val = doStuff3(val); third(); } val = doStuff1(val); second(); } // first(); }). } // Run and get value const val = await fnStack(5); // We get our result ones console;log(val); })();
我認為你應該使用 arrays;
const funcs = [first, second, third];
for(let i = 0; i < funcs.length; ++i){
const result = funcs[i]();
if (result) break;
}
您應該只從具有結果的 function return
。
稍后,當我開始處理它時,我將在此處添加說明,說明如何使用我用於我的轉譯器的抽象語法樹生成庫,甚至可能從另一個版本開始,也許更詳細地解釋什么是事情是讓我認為這是更多的工作。
舊版本如下(即將刪除)
如果該功能被多次使用,我們將需要這樣的東西:
function NestedThrowee(funcName, value){
this.funcName = funcName;
this.value = value;
}
return.someFunctionName someReturnValue
(編譯前)將給出(編譯后)類似
var toBeThrown = new NestedThrowee("someFunctionName", someReturnValue);
在生成的catch
塊內
if (e instanceof Error){
throw e; // re-throw "genuine" Error
} else {
if (e instance of NestedThrowee){
if (e.funcName === ... the name of the function to which
this catch block here belongs ...) return e.value;
throw new Error('something happened which mathheadinclouds deemed impossible');
}
在一般情況下,有必要像上面顯示的那樣包裝結果(或 'throwee'),因為可能有多個,可能嵌套的“嵌套返回”,我們必須注意標語和catch
類型的 object NestedThrowee
匹配(按 function 名稱)。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.