多時候,你想給一個按鈕,在網頁上通過一個實際的打印機打印出網頁的內容。
JavaScript可使用window對象的print函數就可以實現這樣的功能。
當執行JavaScript的print函數window.print()將會打印當前頁面??梢允褂胦nclick事件如下直接調用此函數:
<head>
<script type="text/javascript">
<!--
//-->
</script>
</head>
<body>
<form>
<input type="button" value="Print" onclick="window.print()" />
</form>
</body>1234567891011復制代碼類型:[javascript]
這將產生以下按鈕,打印此頁。
這符合打印出來的頁面,但這個不是一個推薦的方式。打印機友好的頁面實際上只是一個文本,沒有圖像,圖形或廣告頁面。
可以使用以下頁式打印機友好方式:
使頁面的副本,并離開了不需要的文本和圖形,然后從原始鏈接到該打印機友好的頁面。
如果你不想讓頁面的額外副本,那么可以使用像適當的注釋標記打印文本 <!-- PRINT STARTS HERE -->..... <!-- PRINT ENDS HERE --> 然后你可以使用PERL或其他腳本在后臺清除打印文本和顯示進行最后的打印。網站使用同樣的方法給打印設備對我們網站的訪客。
如果沒有人在提供上述設備,那么你可以使用瀏覽器的標準工具欄讓網頁打印出來。按照鏈接如下:
File --> Print --> Click OK button.
開課吧廣場-人才學習交流平臺
文檔生成是開發人員生活中非常普遍的需求。無論是電子商務網站、管理應用程序還是其他任何東西。它可以是發票生成、保險文件準備、醫生處方、人力資源報價生成、工資單生成,你可以想到大量的用例。總是需要生成文檔。
從開發人員的角度來看,有幾種常見的方法可以完成這項工作。
這些方法對我沒有幫助??蛻粝M约憾ㄖ扑麄兊奈募N乙恢痹趯ふ乙环N方法,發現eDocGen是一種單點解決方案。
與其他服務不同,eDocGen 提供了可以集成到我們應用程序中的 RestAPI。
在本文中,我們將討論如何將 eDocGen 集成到我們的 js 應用程序中,以從各種數據格式(如 JSON/XML/Database 模式)生成文檔。請免費試用以開始編碼。
讓我們潛入并編寫代碼。
出于演示目的,我創建了一個在 nodejs 上運行的示例 js 應用程序。
請按照以下步驟為我們設置編碼游樂場。
用于npm init創建 package.json
添加axios, form-data, request,xhr2開發此應用程序所需的依賴項npm install axios form-data request xhr2
我們需要一個索引文件作為我們應用程序的起點。在根目錄中創建一個 index.js 文件并修改 package.json 如下所示。
JSON
scripts": {
"start": "node index.js"
}
現在我們有一個基本的應用程序可以開始。這些步驟結束后,package.json 應該如下所示。
JSON
{
"name": "nodejs-multiple-upload-files",
"version": "1.0.0",
"main": "index.js",
"scripts": {
"start": "node index.js"
},
"dependencies": {
"axios": "^0.27.2",
"form-data": "^4.0.0",
"request": "^2.88.2",
"xhr2": "^0.2.1"
}
}
雖然這篇文章是關于文檔生成的,但我們需要登錄才能獲取我們的訪問令牌。這是一個典型的JWT令牌,將用于授權文檔生成 API。
JavaScript
var XMLHttpRequest = require("xhr2");
var xhr = new XMLHttpRequest();
module.exports.getToken = function (callback) {
var data = JSON.stringify({
username: "<your username>",
password: "<password>",
});
xhr.addEventListener("readystatechange", function () {
if (this.readyState === 4) {
token = JSON.parse(this.responseText).token;
console.log("User Token", token);
callback(token);
}
});
xhr.open("POST", "https://app.edocgen.com/login");
xhr.setRequestHeader("content-type", "application/json");
xhr.setRequestHeader("cache-control", "no-cache");
xhr.send(data);
};
我們可以將令牌在應用程序中緩存一個小于過期時間的時間段,并使用它來生成文檔或上傳模板。到期時間過后,我們可以刷新令牌。緩存可以是 Redis 或內存緩存。這取決于您的應用程序設計。
如上所述,eDocGen 允許用戶自定義和上傳模板。但是如何動態映射數據呢?有一些將數據映射到文檔的規則。我們將看到如何使用規則創建模板。
看看這個文件。
eDocGen{}對動態字段使用由 括起來的標簽。我們可以動態添加文字、logo、表格、條件語句、數學計算等。
例如,在上圖中,
字符串字段: {Invoice_Number}并{Invoice_Date}配置為替換為模板中的文本。模板中 {} 內的任何內容都將與輸入數據匹配并替換。
動態表: 當表中存在需要循環和替換的數據數組時,動態表將是一個不錯的選擇。表中的行以 開頭{#tablename}和結尾{/tablename}。在上面的示例中,發票表中的一行在第一列以 {#IT} 開頭,在最后一列以 {/IT} 結尾。行中的列可以有字符串字段。在我們的示例中,{Item_description}并且{Amount}
圖片: eDocGen 提供動態添加圖片到模板的功能。請按照以下步驟操作。
基于條件的動態字段(If-Else):可以使用條件標簽有條件地顯示內容。例如,當語言為英語時,文檔中會顯示{#language == "english"} 英語內容。同樣,單個文檔模板可以支持多種語言。
數學計算: eDocGen 支持基于模板中定義的公式的數學計算??梢允褂靡韵鹿接嬎惆l票中項目金額的總和。
JSON
{
IT // array of items
| summation:'Amount' // value that needs to be used for calculation
| format_number: ",” // format of the value
}
請前往JSON-to-pdf了解更多詳情。
準備好模板后,就可以將其上傳以供使用。有兩種方法。
對于演示,我使用 UI 來上傳模板。成功上傳后,我們會得到一個 ID 作為響應。這是將用于生成文檔的 ID。
如果您希望使用 API,請在此處留下 Upload API 結構供您參考。
JSON
"/api/v1/document": {
"post": {
"tags": [
"Document"
],
"description": "Upload template to eDocGen",
"produces": [
"application/json"
],
"consumes": [
"multipart/form-data"
],
"parameters": [
{
"name": "documentFile",
"description": "file to upload",
"required": true,
"type": "file",
"in": "formData"
},
{
"name": "x-access-token",
"in": "header",
"description": "JWT auth token from login",
"required": true,
"type": "string"
}
],
"responses": {
"200": {
"description": "Successfully uploaded document file"
},
"other": {
"description": "Operation failed"
}
}
}
}
現在我們準備好了模板。讓我們生成文檔。
文檔生成有兩個階段。
我們要求生成包含所需詳細信息的文檔,并得到確認。該過程異步發生在屏幕后面。
應用程序接口:POST-/api/v1/document/generate/bulk
表格數據
文檔 ID | 模板的id |
格式 | pdf/docx(模板應支持格式) |
輸出文件名 | 輸出文件的文件名。 |
輸入文件 | 該文件包含標記值。支持 json、xlsx 和 xml。 |
內容類型 | 多部分/表單數據 |
x-訪問令牌 | 來自登錄的 JWT 身份驗證令牌 |
inputFile 中的數據應該是模板定義的結構。例如,對于上面的模板映射將如下所示。
可以使用從上述步驟中獲得的輸出 ID 和輸出文件的名稱下載生成的文檔。
我們將在這里使用兩個 API。
由于文檔生成是異步發生的,要知道文檔是否生成,我們將使用/api/v1/output/nameapi。
來自 API 的成功響應/api/v1/output/name將下載文件。
我將這兩個步驟組合在一個 js 文件中,如下所示。
爪哇
let login = require("../edocgen_login");
const fs = require("fs");
const uuid = require("uuid");
const FormData = require("form-data");
let axios = require("axios");
let fileName = uuid.v4();
const headers = {
"Content-Type": "multipart/form-data",
"x-access-token": "null",
};
const hostName = "https://app.edocgen.com/api/v1/document/generate/bulk";
const outputFormat = "<format>";// pdf / docx
const documentId = "<template_id>"; // id of the template we want to use
module.exports.generateFiles = function () {
let authToken = login.getToken(function handleUsersList(token) {
headers["x-access-token"] = token;
var formBody = new FormData();
formBody.append("documentId", documentId);
formBody.append("format", outputFormat);
formBody.append("outputFileName", fileName);
// json data for the template
formBody.append("inputFile", fs.createReadStream("./JSON_Data_Single.json")); // local path forjson file
let config = {
method: "post",
url: hostName,
headers: headers,
data: formBody,
};
console.log(`https://app.edocgen.com/api/v1/output/name/${fileName}.${outputFormat}`);
let config_output = {
method: "get",
url:`https://app.edocgen.com/api/v1/output/name/${fileName}.${outputFormat}`,
headers: headers,
};
const MAX_RETRY = 50;
let currentRetry = 0;
// max retry for 50 times
function errorHandler() {
if (currentRetry < MAX_RETRY) {
currentRetry++;
console.log("Document is not prepared yet! Retrying...");
sendWithRetry(processResponse);
} else {
console.log("No luck. Document is not generated. Retried multiple times.");
}
}
// sendWithRetry checks for file existence
// on success, it proceeds to download the file
// on failure, it retries
// todo: introduce spin lock
function sendWithRetry(callback) {
axios(config_output)
.then(function (response) {
if (response.data.output.length !== 1) {
throw new axios.Cancel("Document is not found. Throw error.");
} else {
callback(response);
}
})
.catch(errorHandler);
}
axios(config)
.then(function (response) {
sendWithRetry(processResponse);
})
.catch(function (error) {
console.log(error);
});
});
};
function processResponse(response) {
const outputId = response.data.output[0]._id;
console.log(
"Output Document is Generated. Id = ",
response.data.output[0]._id
);
let config_download = {
method: "get",
url: `https://app.edocgen.com/api/v1/output/download/${outputId}`,
headers: headers,
responseType: "arraybuffer",
};
axios(config_download)
.then(function (response) {
console.log("Output file is downloaded " + `${fileName}.${outputFormat}`);
fs.writeFileSync(`./${fileName}.${outputFormat}`, response.data);
})
.catch(function (error) {
console.log("Error while downloading");
console.log(error);
});
}
當數據為單個 JSON 時,將生成給定格式的單個文檔。
當數據是對象數組時,將生成每個數組元素的文檔并將其壓縮到文件中。
XML 數據的過程很簡單。我們需要做的就是傳遞 XML 文件來代替 JSON 數據。
就像JSON to document,XML to Document 我們也需要documentId, outputFileName, format and inputFile。除輸入文件外,與 JSON 相同的所有內容都將是 XML 文件。
示例 XML 數據如下所示
XML
<?xml version="1.0" encoding="UTF-8" ?>
<marker>
<values>
<Invoice_Number>SBU-2053501</Invoice_Number>
<Invoice_Date>31-07-2020</Invoice_Date>
<Terms_Payment>Net 15</Terms_Payment>
<Company_Name>ABC company</Company_Name>
<Billing_Contact>ABC-Contact1</Billing_Contact>
<Address>New york, United State</Address>
<Email>support@edocgen.com</Email>
<Logo>621cd2b783a6095d7b15a443</Logo>
<Sum1>6,751</Sum1>
<para>61b334ee7c00363e11da3439</para>
<ITH>
<Heading1>Item Description</Heading1>
<Heading2>Amount</Heading2>
</ITH>
<IT>
<Item_Description>Product Fees: X</Item_Description>
<Amount>5,000</Amount>
</IT>
</values>
<marker>
我為 XML 作為數據源所做的代碼更改很簡單,如下所示
JavaScript
var formBody = new FormData();
formBody.append("documentId", documentId);
formBody.append("format", outputFormat);
formBody.append("outputFileName", fileName);
formBody.append("inputFile", fs.createReadStream("./XML_Invoice.xml"));
從數據庫生成文檔幾乎與其他數據源相同。但在這種情況下,我們需要提供連接詳細信息和 SQL 查詢,而不是上傳 inputFile。
SQL 查詢的輸出列應與文檔模板中的標簽匹配。
讓我們看看如何在代碼中進行配置。
JavaScript
const templateId = "<template id>";
const dbVendor = "mysql";
const dbUrl = "<jdbc connection URL>";
const dbLimit = "100";
const dbPassword = "<database password>";
const dbQuery = "SELECT JSON_ARRAY(first, last) FROM customers;";
const outputFormat = "pdf";
// form data prepareation
let formBody = new FormData();
formBody.append("documentId", templateId);
formBody.append("format", outputFormat);
formBody.append("dbVendor", dbVendor);
formBody.append("dbUrl", dbUrl);
formBody.append("dbLimit", dbLimit);
formBody.append("dbPassword", dbPassword);
formBody.append("dbQuery", dbQuery);
formBody.append("outputFileName", fileName);
其他一切都將保持不變。
eDocGen 提供了通過電子郵件發送生成的文檔的功能。
應用程序接口:POST-/api/v1/output/email
出局 | 將需要通過電子郵件發送的輸出 ID 放在這里 |
電子郵件ID | 將用戶電子郵件放在這里 |
內容類型 | 多部分/表單數據 |
x-訪問令牌 | 來自登錄的 JWT 身份驗證令牌 |
let login = require("../edocgen_login");
let axios = require("axios");
const hostName = "https://app.edocgen.com/api/v1/output/email";
const headers = {
"Content-Type": "application/json",
"x-access-token": "null",
};
const outId = "<output ID>"; // Put output ID here which need to be sent via email
const emailId = "<user email>"; // Put user email here
module.exports.generateFiles = function () {
let authToken = login.getToken(function handleUsersList(token) {
headers["x-access-token"] = token;
let payload = { outId: outId, emailId: emailId };
let config = {
method: "post",
url: hostName,
headers: headers,
data: payload,
};
axios(config)
.then(function (response) {
console.log("Mail sent");
})
.catch(function (error) {
console.log(error);
});
});
};
來自 eDocGen 的電子郵件如下所示
還有很多其他的功能我在這里無法涵蓋。但我希望這篇文章可以為您提供一個從哪里開始的想法。
容轉載自:https://javascript.info/try-catch
不管你多么的精通編程,有時我們的腳本總還是會有一些錯誤。可能是因為我們的編寫出錯,或是與預期不同的用戶輸入,或是錯誤的的服務端返回或者是其他總總不同的原因。
通常,一段代碼會在出錯的時候“死掉”(停止執行)并在控制臺將異常打印出來。
但是有一種更為合理的語法結構 try..catch,它會在捕捉到異常的同時不會使得代碼停止執行而是可以做一些更為合理的操作。
try..catch 結構由兩部分組成:try 和 catch:
try { // 代碼... } catch (err) { // 異常處理 }
它按照以下步驟執行:
所以,發生在 try {…} 代碼塊的異常不會使代碼停止執行:我們可以在 catch 里面處理異常。
讓我們來看更多的例子。
沒有異常的例子:
try { alert('Start of try runs'); // ...這里沒有異常 alert('End of try runs'); } catch(err) { alert('Catch is ignored, because there are no errors'); } alert("...Then the execution continues");
包含異常的例子:
try { alert('Start of try runs'); // (1) <-- lalala; // 異常,變量未定義! alert('End of try (never reached)'); // (2)} catch(err) { alert(`Error has occured!`); // (3) <--} alert("...Then the execution continues");
try..catch only works for runtime errors
要使得 try..catch 能工作,代碼必須是可執行的,換句話說,它必須是有效的 JavaScript 代碼。
如果代碼包含語法錯誤,那么 try..catch 不能正常工作,例如含有未閉合的花括號:
try { {{{{{{{{{{{{ } catch(e) { alert("The engine can't understand this code, it's invalid"); }
JavaScript 引擎讀取然后執行代碼。發生在讀取代碼階段的異常被稱為 “parse-time” 異常,它們不會被 try..catch 覆蓋到(包括那之間的代碼)。這是因為引擎讀不懂這段代碼。
所以,try..catch 只能處理有效代碼之中的異常。這類異常被稱為 “runtime errors”,有時候也稱為 “exceptions”。
try..catch works synchronously
如果一個異常是發生在計劃中將要執行的代碼中,例如在setTimeout中,那么 try..catch 不能捕捉到:
try { setTimeout(function() { noSuchVariable; // 代碼在這里停止執行 }, 1000); } catch (e) { alert( "won't work" ); }
因為 try..catch 包裹了計劃要執行的 setTimeout 函數。但是函數本身要稍后才能執行,這時引擎已經離開了 try..catch 結構。
要捕捉到計劃中將要執行的函數中的異常,那么 try..catch 必須在這個函數之中:
setTimeout(function() { try { noSuchVariable; // try..catch 處理異常! } catch (e) { alert( "error is caught here!" ); } }, 1000);
當一個異常發生之后,JavaScript 生成一個包含異常細節的對象。這個對象會作為一個參數傳遞給 catch:
try { // ... } catch(err) { // “異常對象”,可以用其他參數名代替 err // ... }
對于所有內置的異常,catch 代碼塊捕捉到的相應的異常的對象都有兩個屬性:
name : 異常名稱,對于一個未定義的變量,名稱是 “ReferenceError”
message : 異常詳情的文字描述。
還有很多非標準的屬性在絕大多數環境中可用。其中使用最廣泛并且被廣泛支持的是:
stack : 當前的調用棧:用于調試的,一個包含引發異常的嵌套調用序列的字符串。
例如:
try { lalala; // 異常,變量未定義! } catch(err) { alert(err.name); // ReferenceError alert(err.message); // lalala 未定義 alert(err.stack); // ReferenceError: lalala 在... 中未定義 // 可以完整的顯示一個異常 // 可以轉化成 "name: message" 形式的字符串 alert(err); // ReferenceError: lalala 未定義 }
使用 “try..catch”
讓我們一起探究一下真實使用場景中 try..catch 的使用。
正如我們所知,JavaScript 支持 JSON.parse(str) 方法來解析 JSON 編碼的值。
通常,它被用來解析從網絡,從服務器或是從其他來源收到的數據。
我們收到數據后,像下面這樣調用 JSON.parse:
let json = '{"name":"John", "age": 30}'; // 來自服務器的數據let user = JSON.parse(json); // 將文本表示轉化成 JS 對象// 現在 user 是一個解析自 json 字符串的有自己屬性的對象 alert( user.name ); // John alert( user.age ); // 30
如果 json 格式錯誤,JSON.parse 就會報錯,代碼就會停止執行。
得到報錯之后我們就應該滿意了嗎?當然不!
如果這樣做,當拿到的數據出錯,用戶就不會知道(除非他們打開開發者控制臺)。代碼執行失敗卻沒有提示信息會導致糟糕的用戶體驗。
讓我們來用 try..catch 來處理這個錯誤:
let json = "{ bad json }"; try { let user = JSON.parse(json); // <-- 當這里拋出異常... alert( user.name ); // 不工作 } catch (e) { // ...跳到這里繼續執行 alert( "Our apologies, the data has errors, we'll try to request it one more time." ); alert( e.name ); alert( e.message ); }
我們用 catch 代碼塊來展示信息,但是我們可以做的更多:發送一個新的網絡請求,給用戶提供另外的選擇,把異常信息發送給記錄日志的工具,… 。所有這些都比讓代碼直接停止執行好的多。
拋出自定義的異常
如果這個 json 數據語法正確,但是少了我們需要的 name 屬性呢?
像這樣:
let json = '{ "age": 30 }'; // 不完整的數據 try { let user = JSON.parse(json); // <-- 不拋出異常 alert( user.name ); // 沒有 name! } catch (e) { alert( "doesn't execute" ); }
這里 JSON.parse 正常執行,但是缺少 name 屬性對我們來說確實是個異常。
為了統一的異常處理,我們會使用 throw 運算符。
throw 運算符生成異常對象。
語法如下:
throw <error object>
技術上講,我們可以使用任何東西來作為一個異常對象。甚至可以是基礎類型,比如數字或者字符串。但是更好的方式是用對象,尤其是有 name 和 message 屬性的對象(某種程度上和內置的異常有可比性)。
JavaScript 有很多標準異常的內置的構造器:Error、 SyntaxError、ReferenceError、TypeError 和其他的。我們也可以用他們來創建異常對象。
他們的語法是:
let error = new Error(message);// 或者let error = new SyntaxError(message);let error = new ReferenceError(message);// ...
對于內置的異常對象(不是對于其他的對象,而是對于異常對象),name 屬性剛好是構造器的名字。message 則來自于參數。
例如:
let error = new Error("Things happen o_O"); alert(error.name); // Error alert(error.message); // Things happen o_O
讓我們來看看 JSON.parse 會生成什么樣的錯誤:
try { JSON.parse("{ bad json o_O }"); } catch(e) { alert(e.name); // SyntaxError alert(e.message); // Unexpected token o in JSON at position 0 }
如我們所見, 那是一個 SyntaxError。
假定用戶必須有一個 name 屬性,在我們看來,該屬性的缺失也可以看作語法問題。
所以,讓我們拋出這個異常。
let json = '{ "age": 30 }'; // 不完整的數據 try { let user = JSON.parse(json); // <-- 沒有異常 if (!user.name) { throw new SyntaxError("Incomplete data: no name"); // (*) } alert( user.name ); } catch(e) { alert( "JSON Error: " + e.message ); // JSON Error: Incomplete data: no name }
在 (*) 標記的這一行,throw 操作符生成了包含著我們所給的 message 的 SyntaxError,就如同 JavaScript 自己生成的一樣。try 里面的代碼執行停止,控制權轉交到 catch 代碼塊。
現在 catch 代碼塊成為了處理包括 JSON.parse 在內和其他所有異常的地方。
再次拋出異常
上面的例子中,我們用 try..catch 處理沒有被正確返回的數據,但是也有可能在 try {…} 代碼塊內發生另一個預料之外的異常,例如變量未定義或者其他不是返回的數據不正確的異常。
例如:
let json = '{ "age": 30 }'; // 不完整的數據 try { user = JSON.parse(json); // <-- 忘了在 user 前加 "let" // ... } catch(err) { alert("JSON Error: " + err); // JSON Error: ReferenceError: user is not defined // ( 實際上并沒有 JSON Error) }
當然,一切皆有可能。程序員也是會犯錯的。即使是一些開源的被數百萬人用了幾十年的項目 —— 一個嚴重的 bug 因為他引發的嚴重的黑客事件被發現(比如發生在 ssh 工具上的黑客事件)。
對我們來說,try..catch 是用來捕捉“數據錯誤”的異常,但是 catch 本身會捕捉到所有來自于 try 的異常。這里,我們遇到了預料之外的錯誤,但是仍然拋出了 “JSON Error” 的信息,這是不正確的,同時也會讓我們的代碼變得更難調試。
幸運的是,我們可以通過其他方式找出這個異常,例如通過它的 name 屬性:
try { user = { /*...*/ }; } catch(e) { alert(e.name); // "ReferenceError" for accessing an undefined variable }
規則很簡單:
catch 應該只捕獲已知的異常,而重新拋出其他的異常。
“rethrowing” 技術可以被更詳細的理解為:
在下面的代碼中,為了達到只在 catch 代碼塊處理 SyntaxError 的目的,我們使用重新拋出的方法:
let json = '{ "age": 30 }'; // 不完整的數據 try { let user = JSON.parse(json); if (!user.name) { throw new SyntaxError("Incomplete data: no name"); } blabla(); // 預料之外的異常 alert( user.name ); } catch(e) { if (e.name == "SyntaxError") { alert( "JSON Error: " + e.message ); } else { throw e; // rethrow (*) } }
(*) 標記的這行從 catch 代碼塊拋出的異常,是獨立于我們期望捕獲的異常之外的,它也能被它外部的 try..catch 捕捉到(如果存在該代碼塊的話),如果不存在,那么代碼會停止執行。
所以,catch 代碼塊只處理已知如何處理的異常,并且跳過其他的異常。
下面這段代碼將演示,這種類型的異常如何被另外一層 try..catch 代碼捕獲。
function readData() { let json = '{ "age": 30 }'; try { // ... blabla(); // 異常! } catch (e) { // ... if (e.name != 'SyntaxError') { throw e; // 重新拋出(不知道如何處理它) } } } try { readData(); } catch (e) { alert( "External catch got: " + e ); // 捕獲到! }
例子中的 readData 只能處理 SyntaxError,而外層的 try..catch 能夠處理所有的異常。
然而,這并不是全部。
try..catch 還有另外的語法:finally
如果它有被使用,那么,所有條件下都會執行:
該擴展語法如下所示:
try { ... 嘗試執行的代碼 ... } catch(e) { ... 異常處理 ... } finally { ... 最終會執行的代碼 ... }
試試運行這段代碼:
try { alert( 'try' ); if (confirm('Make an error?')) BAD_CODE(); } catch (e) { alert( 'catch' ); } finally { alert( 'finally' ); }
這段代碼有兩種執行方式:
finally 的語法通常用在:我們在 try..catch 之前開始一個操作,不管在該代碼塊中執行的結果怎樣,我們都想結束的時候執行某個操作。
比如,生成斐波那契數的函數 fib(n) 的執行時間,通常,我們在開始和結束的時候測量。但是,如果該函數在被調用的過程中發生異常,就如執行下面的代碼就會返回負數或者非整數的異常。
任何情況下,finally 代碼塊就是一個很好的結束測量的地方。
這里,不管前面的代碼正確執行,或者拋出異常,finally 都保證了正確的時間測量。
let num = +prompt("Enter a positive integer number?", 35)let diff, result;function fib(n) { if (n < 0 || Math.trunc(n) != n) { throw new Error("Must not be negative, and also an integer."); } return n <= 1 ? n : fib(n - 1) + fib(n - 2);}let start = Date.now();try { result = fib(num);} catch (e) { result = 0;} finally { diff = Date.now() - start;} alert(result || "error occured"); alert( `execution took ${diff}ms` );
你可以通過后面的不同的輸入來檢驗上面代碼的執行:先在 prompt 彈框中先輸入 35 —— 它會正常執行,try 代碼執行后執行 finally 里面的代碼。然后再輸入 -1,會立即捕獲一個異常,執行時間將會是 0ms。兩次的測量結果都是正確的。
換句話說,有兩種方式退出這個函數的執行:return 或是 throw,finally 語法都能處理。
Variables are local inside try..catch..finally
請注意:上面代碼中的 result 和 diff 變量,都需要在 try..catch 之前聲明。
否則,如果用 let 在 {…} 代碼塊里聲明,那么只能在該代碼塊訪問到。
finally and return
finally 語法支持任何的結束 try..catch 執行的方式,包括明確的 return。
下面就是 try 代碼塊包含 return 的例子。在代碼執行的控制權轉移到外部代碼之前,finally 代碼塊會被執行。
function func() { try { return 1; } catch (e) { /* ... */ } finally { alert( 'finally' ); }} alert( func() ); // 先 alert "finally" 里面的內容,再執行這里 try..finally
try..finally 結構也很有用,當我們希望確保代碼執行完成不想在這里處理異常時,我們會使用這種結構。
function func() { // 開始做需要被完成的操作(比如測量) try { // ... } finally { // 完成前面要做的事情,即使 try 里面執行失敗 }}
上面的代碼中,由于沒有 catch,try 代碼塊中的異常會跳出這塊代碼的執行。但是,在跳出之前 finally 里面的代碼會被執行。
Environment-specific
這個部分的內容并不是 JavaScript 核心的一部分。
設想一下,try..catch 之外出現了一個嚴重的異常,代碼停止執行,可能是因為編程異常或者其他更嚴重的異常。
那么,有沒辦法來應對這種情況呢?我們希望記錄這個異常,給用戶一些提示信息(通常,用戶是看不到提示信息的),或者做一些其他操作。
雖然沒有這方面的規范,但是代碼的執行環境一般會提供這種機制,因為這真的很有用。例如,Node.JS 有 process.on(‘uncaughtException’) 。對于瀏覽器環境,我們可以綁定一個函數到 window.onerror,當遇到未知異常的時候,它就會執行。
語法如下:
window.onerror = function(message, url, line, col, error) { // ...};
message : 異常信息。
url : 發生異常的代碼的 URL。
line, col : 錯誤發生的代碼的行號和列號。
error : 異常對象。
例如:
<script> window.onerror = function(message, url, line, col, error) { alert(`${message}\n At ${line}:${col} of ${url}`); }; function readData() { badFunc(); // 哦,出問題了! } readData();</script>
window.onerror 的目的不是去處理整個代碼的執行中的所有異常 —— 這幾乎是不可能的,這只是為了給開發者提供異常信息。
也有針對這種情況提供異常日志的 web 服務,比如 https://errorception.com 或者 http://www.muscula.com。
它們會這樣運行:
try..catch 結構允許我們處理執行時的異常,它允許我們嘗試執行代碼,并且捕獲執行過程中可能發生的異常。
語法如下:
try { // 執行此處代碼 } catch(err) { // 如果發生異常,跳到這里 // err 是一個異常對象 } finally { // 不管 try/catch 怎樣都會執行 }
可能會沒有 catch 代碼塊,或者沒有 finally 代碼塊。所以 try..catch 或者 try..finally 都是可用的。
異常對象包含下列屬性:
我們也可以通過使用 throw 運算符來生成自定義的異常。技術上來講,throw 的參數沒有限制,但是通常它是一個繼承自內置的 Error 類的異常對象。更對關于異常的擴展,請看下個章節。
重新拋出異常,是一種異常處理的基本模式:catch 代碼塊通常處理某種已知的特定類型的異常,所以它應該拋出其他未知類型的異常。
即使我們沒有使用 try..catch,絕大多數執行環境允許我們設置全局的異常處理機制來捕獲出現的異常。瀏覽器中,就是 window.onerror。
*請認真填寫需求信息,我們會在24小時內與您取得聯系。