成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

【譯】Node.js 前端開(kāi)發(fā)指南

CntChen / 1618人閱讀

摘要:定時(shí)器在和瀏覽器中的表現(xiàn)形式是相同的。關(guān)于定時(shí)器的一個(gè)重要的事情是,我們提供的延遲不代表在這個(gè)時(shí)間之后回調(diào)就會(huì)被執(zhí)行。它的真正含義是,一旦主線程完成所有操作包括微任務(wù)并且沒(méi)有其它具有更高優(yōu)先級(jí)的定時(shí)器,將在此時(shí)間之后執(zhí)行回調(diào)。

眾成翻譯

原文鏈接

關(guān)于作者

2018年6月21日出版
?

本指南面向了解Javascript但尚未十分熟悉Node.js的前端開(kāi)發(fā)人員。我這里不專注于語(yǔ)言本身 -- Node.js 使用 V8 引擎,所以和Google Chrome的解釋器是一樣的,這點(diǎn)您或許已經(jīng)了解(但是,它也可以在不同的VM上運(yùn)行,請(qǐng)參閱 node-chakracore)
目錄

Node 版本

不需要Babel

回調(diào)風(fēng)格

事件循環(huán)

事件發(fā)射器

模塊系統(tǒng)

環(huán)境變量

綜合運(yùn)用

總結(jié)

?我們經(jīng)常跟Node.js打交道,即使你是一名前端開(kāi)發(fā)人員 -- npm腳本,webpack配置,gulp任務(wù),程序打包 或 運(yùn)行測(cè)試等。即使你真的不需要深入理解這些任務(wù),但有時(shí)候你會(huì)感到困惑,會(huì)因?yàn)槿鄙貼ode.js的一些核心概念而以非常奇怪的方式來(lái)編碼。熟悉Node.js之后,您還可以讓某些原本需要手動(dòng)操作的東西自動(dòng)執(zhí)行,讓您可以更自信地查看服務(wù)器端代碼,并??編寫更復(fù)雜的腳本。
?

Node 版本

Node.js與客戶端代碼最大的區(qū)別在于您可以根據(jù)運(yùn)行環(huán)境來(lái)決定,并且可以完全清楚它支持哪些特性 -- 您可以根據(jù)具體的需求和可用的服務(wù)器來(lái)選擇使用哪個(gè)版本。

Node.js有一個(gè)公開(kāi)發(fā)布時(shí)間表,告訴我們奇數(shù)版本沒(méi)有被長(zhǎng)期支持。當(dāng)前的LTS(long-term support)版本將被積極開(kāi)發(fā)到2019年4月,然后2019年12月31日之前,通過(guò)更新關(guān)鍵代碼進(jìn)行維護(hù)。Node.js新版本正在積極開(kāi)發(fā),它們帶來(lái)了許多新功能,以及安全性和性能方面的提升。這也許是使用當(dāng)前活躍版本的一個(gè)好理由。然而,沒(méi)有人真正強(qiáng)迫你,如果你不想這樣做,使用舊版本也可以,等到您覺(jué)得時(shí)機(jī)合適再更新就行。

Node.js被廣泛應(yīng)用于現(xiàn)代前端工具鏈 - 我們很難想象一個(gè)現(xiàn)代項(xiàng)目沒(méi)有使用Node工具進(jìn)行任何處理。因此,您可能已經(jīng)熟悉nvm(node版本管理器),它允許你同時(shí)安裝幾個(gè)Node版本,為每個(gè)項(xiàng)目選擇正確的版本。使用這種工具的原因在于,不同項(xiàng)目經(jīng)常使用不同的Node版本,并且你不想永遠(yuǎn)保持它們同步,您只想保留編寫和測(cè)試它們的環(huán)境。其它語(yǔ)言也有很多這樣的工具,例如用于Python的virtualenv,用于Ruby的rbenv等等。

不需要Babel

由于您可以自由選擇任何Node.js版本,所以您很有可能使用LTS版本。該版本在本文撰寫時(shí)為8.11.3,幾乎支持所有ECMAScript 2015的規(guī)范,除了尾遞歸。

這意味著我們不需要Babel,除非您遇到一個(gè)非常舊的Node.js版本,需要轉(zhuǎn)換JSX,或者需要其它前沿的轉(zhuǎn)換器。在實(shí)踐中,Babel并不是那么重要,所以您運(yùn)行的代碼可以和編寫的代碼相同,不需要任何編譯器 -- 這個(gè)我們已經(jīng)遺忘的客戶端天才。

我們也不需要webpack或browserify,那么我們就沒(méi)有工具來(lái)重新加載我們的代碼 -- 如果您在開(kāi)發(fā)類似Web服務(wù)器的東西,您可以使用nodemon,在文件更改后來(lái)重新加載您的應(yīng)用程序。

而且因?yàn)槲覀儾辉谌魏蔚胤絺魉痛a,所以不需要縮小它 -- 省了一步:您只需原封不動(dòng)地使用代碼,真的很神奇!

回調(diào)風(fēng)格

以前,Node.js中的異步函數(shù)接受帶有簽名(err,data)的回調(diào),其中第一個(gè)參數(shù)代表錯(cuò)誤信息 - 如果它為null,則全部正確,否則您必須處理錯(cuò)誤。這些處理程序會(huì)在操作完成,我們得到響應(yīng)后調(diào)用。例如,讓我們讀取一個(gè)文件:

const fs = require("fs");
fs.readFile("myFile.js", (err, file) => {
  if (err) {
    console.error("There was an error reading file :(");
    // process is a global object in Node
   // https://nodejs.org/api/process.html#process_process_exit_code
   process.exit(1);
  }

    // do something with file content
});

我們很快就發(fā)現(xiàn),這種風(fēng)格很難編寫可讀和可維護(hù)的代碼,甚至造成回調(diào)地獄。后來(lái),一種新的原生的異步處理方式 Promise被引入了。它在ECMAScript 2015上標(biāo)準(zhǔn)化(是瀏覽器和Node.js運(yùn)行時(shí)的全局對(duì)象)。近來(lái),async / await 在ECMAScript 2017中標(biāo)準(zhǔn)化了,Node.js 7.6+ 都支持這個(gè)規(guī)范,所以您可以在LTS版本中使用它。

有了 Promise,我們避免了“回調(diào)地獄”。但是,現(xiàn)在我們遇到的問(wèn)題是舊代碼和許多內(nèi)置模塊仍然使用回調(diào)的方式。將它們轉(zhuǎn)換為 Promise 并不是很難 -- 為了闡釋清楚,我們將fs.readFile轉(zhuǎn)成Promise

const fs = require("fs");
function readFile(...arguments) {
  return new Promise((resolve, reject) => {
    fs.readFile(...arguments, (err, data) => {
      if (err) {
         reject(err);
        } else {
          resolve(data);
        }
    });
  });
}

這種模式可以很容易地?cái)U(kuò)展到任何函數(shù),并且內(nèi)置的utils模塊中有一個(gè)特殊的函數(shù) - utils.promisify。官方文檔中的示例:

const util = require("util");
const fs = require("fs");
const stat = util.promisify(fs.stat);

stat(".").then((stats) => {
  // Do something with stats
}).catch((error) => {
  // Handle the error.
});

Node.js核心團(tuán)隊(duì)明白我們需要從舊風(fēng)格中遷移出來(lái),他們嘗試引入一個(gè)內(nèi)置模塊的promisified版本 - 已經(jīng)有promisified文件系統(tǒng)模塊了,雖然寫這篇文章時(shí)它還在處于試驗(yàn)階段。

你仍然會(huì)遇到很多舊式的、帶回調(diào)的Node.js代碼,為了保持一致性,建議使用 utils.promisify 把它們包裝一下。

事件循環(huán)

事件循環(huán)幾乎與在瀏覽器環(huán)境下一樣,只是有一些擴(kuò)展。然而,由于這個(gè)主題比較高深,我將全面講解下,不僅僅是差異(我會(huì)重點(diǎn)強(qiáng)調(diào)這部分,讓您知道哪些是Node.js特有的)。

Node.js中的事件循環(huán)

JavaScript在構(gòu)建時(shí)考慮了異步行為,因此我們通常不會(huì)馬上執(zhí)行所有操作。以下列舉的方法,事件不會(huì)直接按順序執(zhí)行:

microtasks

例如,立即處理Promises,如Promise.resolve。它意味著這段代碼會(huì)在同一個(gè)的事件循環(huán)中被執(zhí)行,但得等到所有同步代碼執(zhí)行完后。

process.nextTick

這是Node.js特有的方法,它不存在于任何瀏覽器(以及進(jìn)程對(duì)象)中。它的行為類似于微任務(wù)(microtask),但具有優(yōu)先級(jí)。這意味著它將在所有同步代碼之后立即執(zhí)行,即使之前引入了其他微任務(wù) - 這是很危險(xiǎn)的,可能導(dǎo)致無(wú)限循環(huán)。從命名上講是不對(duì)的,因?yàn)樗窃谕粋€(gè)事件循環(huán)中執(zhí)行的,而不是在它的next tick中執(zhí)行。但是由于兼容性原因,它可能保持不變。

setImmediate

雖然它確實(shí)存在于某些瀏覽器中,但并未在所有瀏覽器中達(dá)到一致的行為,因此在瀏覽器中使用時(shí),您需要非常小心。它類似于 setTimeout(0)代碼,但有時(shí)會(huì)優(yōu)先于它。這里的命名也不是最好的 - 我們?cè)谡務(wù)撓乱粋€(gè)事件循環(huán)迭代,它并不是真正的immidiate。

setTimeout/setInterval

定時(shí)器在Node和瀏覽器中的表現(xiàn)形式是相同的。關(guān)于定時(shí)器的一個(gè)重要的事情是,我們提供的延遲不代表在這個(gè)時(shí)間之后回調(diào)就會(huì)被執(zhí)行。它的真正含義是,一旦主線程完成所有操作(包括微任務(wù))并且沒(méi)有其它具有更高優(yōu)先級(jí)的定時(shí)器,Node.js將在此時(shí)間之后執(zhí)行回調(diào)。

讓我們看看這個(gè)例子:

往下看我會(huì)給出腳本執(zhí)行后正確的輸出,但是如果你愿意,請(qǐng)嘗試自己完成它(當(dāng)一回“JavaScript解釋器”):

const fs = require("fs");
console.log("beginning of the program");
const promise = new Promise(resolve => {
  // function, passed to the Promise constructor
  // is executed synchronously!
  console.log("I am in the promise function!");
resolve("resolved message");
});
promise.then(() => {
  console.log("I am in the first resolved promise");
}).then(() => {
  console.log("I am in the second resolved promise");
});
process.nextTick(() => {
  console.log("I am in the process next tick now");
});
fs.readFile("index.html", () => {
  console.log("==================");
setTimeout(() => {
    console.log("I am in the callback from setTimeout with 0ms delay");
}, 0);
setImmediate(() => {
    console.log("I am from setImmediate callback");
});
});
setTimeout(() => {
  console.log("I am in the callback from setTimeout with 0ms delay");
}, 0);
setImmediate(() => {
  console.log("I am from setImmediate callback");
});

正確的執(zhí)行順序如下:

node event-loop.js
beginning of the program
I am in the promise function!
I am in the process next tick now
I am in the first resolved promise
I am in the second resolved promise
I am in the callback from setTimeout with 0ms delay
I am from setImmediate callback
==================
I am from setImmediate callback
I am in the callback from setTimeout with 0ms delay

您可以在Node.js官方文檔中獲取更多有關(guān)事件循環(huán)和process.nextTick的信息。

事件發(fā)射器

Node.js中的許多核心模塊派發(fā)或接收不同的事件。它有一個(gè)EventEmitter的實(shí)現(xiàn),是一個(gè)發(fā)布 - 訂閱模式。這與瀏覽器DOM事件非常相似,語(yǔ)法略有不同,理解它最好的方式就是親自來(lái)實(shí)現(xiàn)一下:

class EventEmitter {
  constructor() {
    this.events = {};
}
  checkExistence(event) {
    if (!this.events[event]) {
      this.events[event] = [];
    }
  }
  once(event, cb) {
    this.checkExistence(event);
    const cbWithRemove = (...args) => {
          cb(...args);
        this.off(event, cbWithRemove);
      };
      this.events[event].push(cbWithRemove);
     }
  on(event, cb) {
    this.checkExistence(event);
    this.events[event].push(cb);
  }
  off(event, cb) {
    this.checkExistence(event);
    this.events[event] = this.events[event].filter(
      registeredCallback => registeredCallback !== cb
    );
  }
  emit(event, ...args) {
    this.checkExistence(event);
    this.events[event].forEach(cb => cb(...args));
    }
  }
以上代碼只顯示模式本身,并沒(méi)有針對(duì)確切的功能 - 請(qǐng)不要在您的代碼中使用它!

這是我們需要的所有基礎(chǔ)代碼!它允許您訂閱事件,稍后取消訂閱,并派發(fā)不同的事件。例如,響應(yīng)體,請(qǐng)求體,流 - 它們實(shí)際上都擴(kuò)展或?qū)崿F(xiàn)了EventEmitter!

正因?yàn)樗且粋€(gè)如此簡(jiǎn)單的概念,所以被用于許多的NPM包。所以,如果你想在瀏覽器中使用相同的事件發(fā)射器,可以隨時(shí)使用它們。

“Streams是Node.js最好用、最容易被誤解的概念?!?/pre>

多米尼克塔爾(Dominic Tarr)

Streams允許您以塊的形式來(lái)處理數(shù)據(jù),而不僅僅是完整操作(如讀取文件)。為了理解它們的作用,讓我們來(lái)看個(gè)簡(jiǎn)單的例子:假設(shè)我們想要向用戶返回任意大小的請(qǐng)求文件。我們的代碼可能如下所示:

function (req, res) {
  const filename = req.url.slice(1);
  fs.readFile(filename, (err, data) => {
    if (err) {
        res.statusCode = 500;
        res.end("Something went wrong");
    } else {
       res.end(data);
    }
  });
}

這段代碼可以使用,特別是在本地開(kāi)發(fā)的機(jī)器上,但它可也能會(huì)失敗 - 您看出問(wèn)題了嗎?如果文件太大,我們讀取文件時(shí)就會(huì)遇到問(wèn)題,我們將所有內(nèi)容放入內(nèi)存中,如果沒(méi)有足夠的內(nèi)存空間,這將無(wú)法正常工作。如果我們有很多并發(fā)請(qǐng)求,這段代碼也不會(huì)生效 - 我們必須將數(shù)據(jù)對(duì)象保留在內(nèi)存中,直到我們發(fā)送了所有內(nèi)容。

然而,我們根本不需要這個(gè)文件 - 我們只需要從文件系統(tǒng)返回它,我們自己不會(huì)查看內(nèi)容,所以我們可以讀取它的一部分,立即返回給客戶端來(lái)釋放我們的內(nèi)存,重復(fù)這樣一個(gè)過(guò)程,直到我們完成了整個(gè)文件的發(fā)送。這是對(duì) Streams 的簡(jiǎn)短介紹 - 我們有一種以塊的形式來(lái)接收數(shù)據(jù)的機(jī)制,并且 我們 決定如何處理這些數(shù)據(jù)。例如,我們同樣可以這樣處理:

function (req, res) {
  const filename = req.url.slice(1);
  const filestream = fs.createReadStream(filename, { encoding: "utf-8" });
  let result = "";
  filestream.on("data", chunk => {
    result += chunk;
  });
  filestream.on("end", () => {
    res.end(result);
  });
  // if file does not exist, error callback will be called
  filestream.on("error", () => {
    res.statusCode = 500;
  res.end("Something went wrong");
  });
}

這里我們創(chuàng)建一個(gè) 來(lái)讀取文件 - 這個(gè)流執(zhí)行EventEmitter這個(gè)類,在data事件上我們接收下一個(gè)塊,在end事件中,我們得到一個(gè)信號(hào),表示流已結(jié)束,然后讀取完整文件。這樣的實(shí)現(xiàn)跟前面的一樣 - 我們等待整個(gè)文件被讀取,然后在響應(yīng)中返回它。此外,它也有同樣的問(wèn)題:我們將整個(gè)文件保留在內(nèi)存中,然后再發(fā)送回來(lái)。如果我們知道響應(yīng)對(duì)象本身實(shí)現(xiàn)了可寫流,我們可以解決這個(gè)問(wèn)題,我們可以將信息寫入該流而不將其保留在內(nèi)存中:

function (req, res) {
  const filename = req.u?rl.slice(1);
  const filestream = fs.createReadStream(filename, { encoding: "utf-8" });
  filestream.on("data", chunk => {
    res.write(chunk);
  });
  filestream.on("end", () => {
    res.end();
  });
  // if file does not exist, error callback will be called
  filestream.on("error", () => {
    res.statusCode = 500;
    res.end("Something went wrong");
  });
}
響應(yīng)體實(shí)現(xiàn)可寫流,fs.createReadStream 創(chuàng)建可讀流,還有雙向和轉(zhuǎn)換流。它們之間的區(qū)別以及工作原理,不在本教程的范圍內(nèi),但是了解它們的存在還是大有裨益的。

這樣我們不再需要結(jié)果變量了,只需要把已讀的 立即寫入響應(yīng)體,不將它保留在內(nèi)存中!這意味著我們甚至可以讀取大文件,而不必?fù)?dān)心高并發(fā)請(qǐng)求 - 因?yàn)槲募](méi)有被保存在內(nèi)存中,所以不會(huì)超出內(nèi)存所能承載的數(shù)量。但是,存在一個(gè)問(wèn)題。在我們的解決方案中,我們從一個(gè)流(文件系統(tǒng)讀取文件)中讀取文件,并將其寫入另一個(gè)(網(wǎng)絡(luò)請(qǐng)求),這兩個(gè)事物具有不同的延遲。這里強(qiáng)調(diào)是真的不同,經(jīng)過(guò)一段時(shí)間后,我們的響應(yīng)流將不堪重負(fù),因?yàn)樗枚?。這個(gè)問(wèn)題是對(duì)背壓的描述,Node有一個(gè)解決方案:每個(gè)可讀流都有一個(gè)管道方法,它將所有數(shù)據(jù)重定向到與其負(fù)載相關(guān)的給定流中:如果它正忙,它將暫停原始流并恢復(fù)它。使用此方法,我們可以將代碼簡(jiǎn)化為:

function (req, res) {
  const filename = req.url.slice(1);
  const filestream = fs.createReadStream(filename, { encoding: "utf-8" });
  filestream.pipe(res);
  // if file does not exist, error callback will be called
  filestream.on("error", () => {
    res.statusCode = 500;
    res.end("Something went wrong");
  });
}
在Node的歷史進(jìn)程中,Streams改變了幾次,所以在閱讀舊手冊(cè)時(shí)要格外小心,并經(jīng)常查看官方文檔!
模塊系統(tǒng)

Node.js使用commonjs模塊。您或許使用過(guò) - 每次使用require來(lái)獲取webpack配置中的某個(gè)模塊時(shí),您實(shí)際上就使用了commonjs模塊; 每次聲明 module.exports 時(shí)也在使用它。然而,您可能還會(huì)看到像 exports.some = {} 這樣的寫法,沒(méi)有 module,在這一節(jié)中我們將看下它究竟是如何工作的。

首先,我們來(lái)討論commonjs模塊,它們通常都有 .js 的擴(kuò)展,而不是 .esm / .mjs 文件(ECMAScript模塊),它們?cè)试S您使用 import/export 的語(yǔ)法。另外,重要的是要明白,webpack和browserify(以及其它打包工具)使用自己的require函數(shù),所以請(qǐng)不要混淆 - 這里不講解它們,只要明白它們是不同的東西就行(即使它們表現(xiàn)得非常相似)。

那么,我們實(shí)際上是在哪里獲得這些“全局”對(duì)象,如 modulerequierexports ?實(shí)際上,是Node.js在運(yùn)行時(shí)添加的 - 它不是僅執(zhí)行給定的javascript文件,實(shí)際上是將它包含在具有所有這些變量的函數(shù)中:

function (exports, require, module, __filename, __dirname) {
  // your module
}

您可以在命令行中執(zhí)行以下代碼段來(lái)查看這個(gè)包:

1node -e "console.log(require("module").wrapper)"

這些是注入到模塊中的變量,可以作為“全局”變量使用,即使它們不是真正的全局變量。我強(qiáng)烈建議你研究它們,尤其是模塊變量。你可以在javascript文件中調(diào)用 console.log(module),對(duì)比從 main 文件打印和從 required 的文件打印出來(lái)的結(jié)果。

接下來(lái),讓我們看一下 exports 對(duì)象 - 這里有一個(gè)小例子,顯示一些與之相關(guān)的警告:

exports.name = "our name";
// this works

exports = { name: "our name" };
// this doesn"t work

module.exports = { name: "our name" };
// this works!

上面的例子可能會(huì)讓你感到困惑 為什么會(huì)這樣?答案是exports對(duì)象的本質(zhì) - 它只是一個(gè)傳遞給函數(shù)的參數(shù),所以在我們給它指定一個(gè)新對(duì)象的情況時(shí),我們只是重寫這個(gè)變量,舊的引用就不存在了。盡管它沒(méi)有完全消失 - module.exports是同一個(gè)對(duì)象 - 所以它們實(shí)際上是對(duì)單個(gè)對(duì)象的相同引用:

module.exports === exports;
// true

最后一部分是 require - 它是一個(gè)獲取模塊名稱并返回該模塊的 exports對(duì)象 的函數(shù)。它究竟是如何解析模塊的?有一個(gè)非常簡(jiǎn)單的規(guī)則:

根據(jù)名稱檢索核心模塊

如果路徑以 ./../開(kāi)頭,則嘗試解析文件

如果找不到文件,嘗試在其中找到包含index.js文件的目錄

如果path 不以 ./../ 開(kāi)頭,請(qǐng)轉(zhuǎn)到node_modules /并檢查文件夾/文件:

在我們運(yùn)行腳本的文件夾中

上面一級(jí),直到我們到達(dá)/ node_modules

還有其它一些位置,主要是為了兼容性,您還可以通過(guò)指定變量 NODE_PATH 來(lái)提供查找路徑,這也許很有用。如果您要查看解析node_modules的確切順序,只需在腳本中打印模塊對(duì)象并查找paths屬性。我操作后,打印了如下內(nèi)容:

? tmp node test.js

Module {
  id: ".",
  exports: {},
  parent: null,
  filename: "/Users/seva.zaikov/tmp/test.js",
  loaded: false,
  children: [],
  paths:
   [ "/Users/seva.zaikov/tmp/node_modules",
     "/Users/seva.zaikov/node_modules",
     "/Users/node_modules",
     "/node_modules" ] }

關(guān)于 require 的另一個(gè)有趣的事情是,在第一個(gè)require調(diào)用模塊被緩存后,將不會(huì)再次執(zhí)行,我們將只返回緩存的export對(duì)象 - 這意味著你可以做一些邏輯并確保它會(huì)在第一次require調(diào)用之后只執(zhí)行一次(這不完全正確 - 如果再次需要,你可以從require.cache中刪除模塊id ,然后重新加載模塊)

環(huán)境變量

正如在十二因素應(yīng)用程序所述,將配置存儲(chǔ)在環(huán)境變量中是一種很好的做法。您可以為shell會(huì)話設(shè)置變量:

export MY_VARIABLE="some variable value"

Node是一個(gè)跨平臺(tái)引擎,理想情況下,您的應(yīng)用程序應(yīng)該可以在任何平臺(tái)上運(yùn)行(例如,開(kāi)發(fā)環(huán)境。您選擇生產(chǎn)環(huán)境來(lái)運(yùn)行您的代碼,通常它是一些Linux分發(fā)版)。我的示例僅涵蓋MacOS / Linux,不適用于Windows。Windows中環(huán)境變量的語(yǔ)法跟這里的不同,你可以使用像cross-env這樣的東西,但在其它情況下,你也應(yīng)該記住這點(diǎn)。

您可以把下面這行代碼添加到 bash / zsh 配置文件中,以便在任何新的終端會(huì)話中進(jìn)行設(shè)置。然而,您通常只在運(yùn)行應(yīng)用程序時(shí),為這些實(shí)例提供特有的變量:

APP_DB_URI="....." SECRET_KEY="secret key value" node server.js

您可以使用 process.env 對(duì)象來(lái)訪問(wèn) Node.js 應(yīng)用程序中的這些變量:

const CONFIG = {
  db: process.env.APP_DB_URI,
  secret: process.env.SECRET_KEY
}
綜合運(yùn)用

在下面的例子中,我們將創(chuàng)建一個(gè)簡(jiǎn)單的http服務(wù),它將返回一個(gè)文件,以u(píng)rl/后面的字符串來(lái)命名。如果文件不存在,我們將返回 404 Not Found 的錯(cuò)誤信息,如果用戶試圖投機(jī)取巧,使用相對(duì)路徑或嵌套路徑,我們則返回403錯(cuò)誤。我們之前使用過(guò)其中的一些函數(shù),但沒(méi)有真正記錄它們 - 這次它將包含大量的信息:

// we require only built-in modules, so Node.js
// does not traverse our node_modules folders
// https://nodejs.org/api/http.html#http_http_createserver_options_requestlistener

const { createServer } = require("http");
const fs = require("fs");
const url = require("url");
const path = require("path");

// we pass the folder name with files as an environment variable
// so we can use a different folder locally

const FOLDER_NAME = process.env.FOLDER_NAME;
const PORT = process.env.PORT || 8080;
const server = createServer((req, res) => {
  // req.url contains full url, with querystring
  // we ignored it before, but here we want to ensure
  // that we only get pathname, without querystring
  // https://nodejs.org/api/http.html#http_message_url
  
  const parsedURL = url.parse(req.url);
  
   // we don"t need the first / symbol
  const pathname = parsedURL.pathname.slice(1);
  
  // in order to return a response, we have to call res.end()
  // https://nodejs.org/api/http.html#http_response_end_data_encoding_callback
  //
  // > The method, response.end(), MUST be called on each response.
  // if we don"t call it, the connection won"t close and a requester
  // will wait for it until the timeout
  // 
  // by default, we return a response with [code 200](https://en.wikipedia.org/wiki/List_of_HTTP_status_codes)
  // in case something went wrong, we are supposed to return
  // a correct status code, using the res.statusCode = ... property:
  // https://nodejs.org/api/http.html#http_response_statuscode

  if (pathname.startsWith(".")) {
    res.statusCode = 403;
     res.end("Relative paths are not allowed");
  } else if (pathname.includes("/")) {
    res.statusCode = 403;
    res.end("Nested paths are not allowed");
  } else {
    // https://nodejs.org/en/docs/guides/working-with-different-filesystems/
    // in order to stay cross-platform, we can"t just create a path on our own
    // we have to use the platform-specific separator as a delimiter
    // path.join() does exactly that for us:
    // https://nodejs.org/api/path.html#path_path_join_paths
    const filePath = path.join(__dirname, FOLDER_NAME, pathname);
  const fileStream = fs.createReadStream(filePath);
  fileStream.pipe(res);
  fileStream.on("error", e => {
      // we handle only non-existant files, but there are plenty
      // of possible error codes. you can get all common codes from the docs:
      // https://nodejs.org/api/errors.html#errors_common_system_errors
      
      if (e.code === "ENOENT") {
       res.statusCode = 404;
        res.end("This file does not exist.");
    } else {
        res.statusCode = 500;
        res.end("Internal server error");
    }
  });}
 });
server.listen(PORT, () => {
  console.log(application is listening at the port ${PORT});
});
總結(jié)

在本指南中,我們介紹了許多基本的Node.js原則。我們沒(méi)有深入研究特定的API,我們確實(shí)錯(cuò)過(guò)了一些東西。但是,本指南應(yīng)該是一個(gè)很好的起點(diǎn),讓您在閱讀API,編輯現(xiàn)有的代碼,或者創(chuàng)建新腳本時(shí)有信心。您現(xiàn)在能夠理解錯(cuò)誤,清楚內(nèi)置模塊使用的接口,以及從典型的Node.js對(duì)象和接口中能獲取到哪些東西。

下一次,我們將深入介紹使用Node.js的Web服務(wù),Node.js REPL,如何編寫CLI應(yīng)用程序,以及如何使用Node.js編寫小腳本。您可以訂閱以獲取有關(guān)這些新文章的通知。

相關(guān)文章
2017年7月9日? Node.js REPL深度

2018年6月5日? 不要使用縮略詞

2018 年 6月3日? 單元測(cè)試

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/108427.html

相關(guān)文章

  • 那些年,我的前端/Java后端書(shū)單

    摘要:全文為這些年,我曾閱讀深入理解過(guò)或正在閱讀學(xué)習(xí)即將閱讀的一些優(yōu)秀經(jīng)典前端后端書(shū)籍。當(dāng)然,如果您喜歡這篇文章,可以動(dòng)手點(diǎn)點(diǎn)贊或者收藏。 全文為這些年,我曾閱讀、深入理解過(guò)(或正在閱讀學(xué)習(xí)、即將閱讀)的一些優(yōu)秀經(jīng)典前端/Java后端書(shū)籍。全文為純?cè)瓌?chuàng),且將持續(xù)更新,未經(jīng)許可,不得進(jìn)行轉(zhuǎn)載。當(dāng)然,如果您喜歡這篇文章,可以動(dòng)手點(diǎn)點(diǎn)贊或者收藏。 基礎(chǔ) 基礎(chǔ)書(shū)籍 進(jìn)階 進(jìn)階階段,深入學(xué)習(xí)的書(shū)...

    fxp 評(píng)論0 收藏0
  • 那些年,我的前端/Java后端書(shū)單

    摘要:全文為這些年,我曾閱讀深入理解過(guò)或正在閱讀學(xué)習(xí)即將閱讀的一些優(yōu)秀經(jīng)典前端后端書(shū)籍。當(dāng)然,如果您喜歡這篇文章,可以動(dòng)手點(diǎn)點(diǎn)贊或者收藏。 全文為這些年,我曾閱讀、深入理解過(guò)(或正在閱讀學(xué)習(xí)、即將閱讀)的一些優(yōu)秀經(jīng)典前端/Java后端書(shū)籍。全文為純?cè)瓌?chuàng),且將持續(xù)更新,未經(jīng)許可,不得進(jìn)行轉(zhuǎn)載。當(dāng)然,如果您喜歡這篇文章,可以動(dòng)手點(diǎn)點(diǎn)贊或者收藏。 基礎(chǔ) 基礎(chǔ)書(shū)籍 進(jìn)階 進(jìn)階階段,深入學(xué)習(xí)的書(shū)...

    Tecode 評(píng)論0 收藏0
  • 那些年,我的前端/Java后端書(shū)單

    摘要:全文為這些年,我曾閱讀深入理解過(guò)或正在閱讀學(xué)習(xí)即將閱讀的一些優(yōu)秀經(jīng)典前端后端書(shū)籍。當(dāng)然,如果您喜歡這篇文章,可以動(dòng)手點(diǎn)點(diǎn)贊或者收藏。 全文為這些年,我曾閱讀、深入理解過(guò)(或正在閱讀學(xué)習(xí)、即將閱讀)的一些優(yōu)秀經(jīng)典前端/Java后端書(shū)籍。全文為純?cè)瓌?chuàng),且將持續(xù)更新,未經(jīng)許可,不得進(jìn)行轉(zhuǎn)載。當(dāng)然,如果您喜歡這篇文章,可以動(dòng)手點(diǎn)點(diǎn)贊或者收藏。 基礎(chǔ) 基礎(chǔ)書(shū)籍 進(jìn)階 進(jìn)階階段,深入學(xué)習(xí)的書(shū)...

    VPointer 評(píng)論0 收藏0
  • 那些年,我的前端/Java后端書(shū)單

    摘要:全文為這些年,我曾閱讀深入理解過(guò)或正在閱讀學(xué)習(xí)即將閱讀的一些優(yōu)秀經(jīng)典前端后端書(shū)籍。當(dāng)然,如果您喜歡這篇文章,可以動(dòng)手點(diǎn)點(diǎn)贊或者收藏。 全文為這些年,我曾閱讀、深入理解過(guò)(或正在閱讀學(xué)習(xí)、即將閱讀)的一些優(yōu)秀經(jīng)典前端/Java后端書(shū)籍。全文為純?cè)瓌?chuàng),且將持續(xù)更新,未經(jīng)許可,不得進(jìn)行轉(zhuǎn)載。當(dāng)然,如果您喜歡這篇文章,可以動(dòng)手點(diǎn)點(diǎn)贊或者收藏。 基礎(chǔ) 基礎(chǔ)書(shū)籍 進(jìn)階 進(jìn)階階段,深入學(xué)習(xí)的書(shū)...

    idealcn 評(píng)論0 收藏0
  • 正在暑假中的《課多周刊》(第1期)

    摘要:正在暑假中的課多周刊第期我們的微信公眾號(hào),更多精彩內(nèi)容皆在微信公眾號(hào),歡迎關(guān)注。若有幫助,請(qǐng)把課多周刊推薦給你的朋友,你的支持是我們最大的動(dòng)力。原理微信熱更新方案漲知識(shí)了,熱更新是以后的標(biāo)配。 正在暑假中的《課多周刊》(第1期) 我們的微信公眾號(hào):fed-talk,更多精彩內(nèi)容皆在微信公眾號(hào),歡迎關(guān)注。 若有幫助,請(qǐng)把 課多周刊 推薦給你的朋友,你的支持是我們最大的動(dòng)力。 遠(yuǎn)上寒山石徑...

    liukai90 評(píng)論0 收藏0
  • 正在暑假中的《課多周刊》(第1期)

    摘要:正在暑假中的課多周刊第期我們的微信公眾號(hào),更多精彩內(nèi)容皆在微信公眾號(hào),歡迎關(guān)注。若有幫助,請(qǐng)把課多周刊推薦給你的朋友,你的支持是我們最大的動(dòng)力。原理微信熱更新方案漲知識(shí)了,熱更新是以后的標(biāo)配。 正在暑假中的《課多周刊》(第1期) 我們的微信公眾號(hào):fed-talk,更多精彩內(nèi)容皆在微信公眾號(hào),歡迎關(guān)注。 若有幫助,請(qǐng)把 課多周刊 推薦給你的朋友,你的支持是我們最大的動(dòng)力。 遠(yuǎn)上寒山石徑...

    yintaolaowanzi 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<