Promise - ES6新對象
Promise能夠處理異步程序。
回調地獄
JS中或node中,都大量的使用了回調函數進行異步操作,而異步操作什么時候返回結果是不可控的,如果我們希望幾個異步請求按照順序來執行,那么就需要將這些異步操作嵌套起來,嵌套的層數特別多,就叫做回調地獄。
下面的案例就有回調地獄的意思:
案例:有 a.txt、b.txt、c.txt三個文件,使用fs模板按照順序來讀取里面的內容,代碼:
// 將讀取的a、b、c里面的內容,按照順序輸出
const fs = require('fs');// 讀取a文件
fs.readFile('./a.txt', 'utf-8', (err, data) => {if (err) throw err;console.log(data.length);// 讀取b文件fs.readFile('./b.txt', 'utf-8', (err, data) => {if (err) throw err;console.log(data);// 讀取c文件fs.readFile('./c.txt', 'utf-8', (err, data) => {if (err) throw err;console.log(data);});});
});
案例中,只有三個文件,試想如果需要按照順序讀取的文件非常多,那么嵌套的代碼將會多的可怕,這就是回調地獄的意思。
Promise簡介
- Promise對象可以解決回調地獄的問題
- Promise 是異步編程的一種解決方案,比傳統的解決方案(回調函數和事件)更合理和更強大
Promise可以理解為一個容器,里面可以編寫異步程序的代碼
- 從語法上說,Promise 是一個對象,使用的使用需要
new
Promise簡單使用
Promise是“承諾”的意思,實例中,它里面的異步操作就相當于一個承諾,而承諾就會有兩種結果,要么完成了承諾的內容,要么失敗。
所以,使用Promise,分為兩大部分,首先是有一個承諾(異步操作),然后再兌現結果。
第一部分:定義“承諾”
// 實例化一個Promise,表示定義一個容器,需要給它傳遞一個函數作為參數,而該函數又有兩個形參,通常用resolve和reject來表示。該函數里面可以寫異步請求的代碼
// 換個角度,也可以理解為定下了一個承諾
let p = new Promise((resolve, reject) => {// 形參resolve,單詞意思是 完成// 形參reject ,單詞意思是 失敗fs.readFile('./a.txt', 'utf-8', (err, data) => {if (err) {// 失敗,就告訴別人,承諾失敗了reject(err);} else {// 成功,就告訴別人,承諾實現了resolve(data.length);} });
});
第二部分:獲取“承諾”的結果
// 通過調用 p 的then方法,可以獲取到上述 “承諾” 的結果
// then方法有兩個函數類型的參數,參數1表示承諾成功時調用的函數,參數2可選,表示承諾失敗時執行的函數
p.then((data) => {},(err) => {}
);
完整的代碼:
const fs = require('fs');
// promise 承諾// 使用Promise分為兩大部分// 1. 定義一個承諾
let p = new Promise((resolve, reject) => {// resolve -- 解決,完成了; 是一個函數// reject -- 拒絕,失敗了; 是一個函數// 異步操作的代碼,它就是一個承諾fs.readFile('./a.txt', 'utf-8', (err, data) => {if (err) {reject(err);} else {resolve(data.length);}});
});// 2. 兌現承諾
// p.then(
// (data) => {}, // 函數類似的參數,用于獲取承諾成功后的數據
// (err) => {} // 函數類型的參數,用于或承諾失敗后的錯誤信息
// );
p.then((data) => {console.log(data);},(err) => {console.log(err);}
);
then方法的鏈式調用
-
前一個then里面返回的字符串,會被下一個then方法接收到。但是沒有意義;
-
前一個then里面返回的Promise對象,并且調用resolve的時候傳遞了數據,數據會被下一個then接收到
-
前一個then里面如果沒有調用resolve,則后續的then不會接收到任何值
const fs = require('fs'); // promise 承諾new Promise((resolve, reject) => {fs.readFile('./a.txt', 'utf-8', (err, data) => {err ? reject(err) : resolve(data.length);}); }) .then((a) => {console.log(a);return new Promise((resolve, reject) => {fs.readFile('./a.txt', 'utf-8', (err, data) => {err ? reject(err) : resolve(data.length);});}); }) .then((b) => {console.log(b);return new Promise((resolve, reject) => {fs.readFile('./a.txt', 'utf-8', (err, data) => {err ? reject(err) : resolve(data.length);});}); }) .then((c) => {console.log(c) }) .catch((err) => {console.log(err); });
catch 方法可以統一獲取錯誤信息
封裝按順序異步讀取文件的函數
function myReadFile(path) {return new Promise((resolve, reject) => {fs.readFile(path, 'utf-8', (err, data) => {err ? reject(err) : resolve(data.length);})});
}myReadFile('./a.txt')
.then((a) => {console.log(a);return myReadFile('./b.txt');
})
.then((b) => {console.log(b);return myReadFile('./c.txt');
})
.then((c) => {console.log(c)
})
.catch((err) => {console.log(err);
});
async 和 await 修飾符
ES6 — ES2015
async 和 await 是 ES2017 中提出來的。
異步操作是 JavaScript 編程的麻煩事,麻煩到一直有人提出各種各樣的方案,試圖解決這個問題。
從最早的回調函數,到 Promise 對象,再到 Generator 函數,每次都有所改進,但又讓人覺得不徹底。它們都有額外的復雜性,都需要理解抽象的底層運行機制。
異步I/O不就是讀取一個文件嗎,干嘛要搞得這么復雜?異步編程的最高境界,就是根本不用關心它是不是異步。
async 函數就是隧道盡頭的亮光,很多人認為它是異步操作的終極解決方案。
ES2017提供了async和await關鍵字。await和async關鍵詞能夠將異步請求的結果以返回值的方式返回給我們。
- async 用于修飾一個 function
- async 修飾的函數,表示該函數里面有異步操作(Promise的調用)
- await和async需要配合使用,沒有async修飾的函數中使用await是沒有意義的,會報錯
- await需要定義在async函數內部,await后面跟的一般都是一個函數(函數里面包含有Promise)的調用
- await修飾的異步操作,可以使用返回值的方式去接收異步操作的結果
- 如果有哪一個await操作出錯了,會中斷async函數的執行
總結來說:async 表示函數里有異步操作,await 表示緊跟在后面的表達式需要等待結果。
const fs = require('fs');
// 將異步讀取文件的代碼封裝
function myReadFile (path) {return new Promise((resolve, reject) => {fs.readFile(path, 'utf-8', (err, data) => {err ? reject(err) : resolve(data.length);});})
}async function abc () {let a = await myReadFile('./a.txt');let b = await myReadFile('./b.txt');let c = await myReadFile('./c.txt');console.log(b);console.log(a);console.log(c);
}abc();
路由
什么是路由
廣義上來講,路由就是映射關系。
程序中的路徑也是映射關系:
Express 中的路由
在 Express 中,路由指的是客戶端的請求與服務器處理函數之間的映射關系。
Express 中的路由分 3 部分組成,分別是請求的類型、請求的 URL 地址、處理函數,格式如下
// 路徑 ,就是我們之前說的接口的處理程序
app.get('/api/getbooks', (req, res) => {});app.post('/api/addbook', (req, res) => {});
每當一個請求到達服務器之后,需要先經過路由的匹配,只有匹配成功之后,才會調用對應的處理函數。
在匹配時,會按照路由的順序進行匹配,如果請求類型和請求的 URL 同時匹配成功,則 Express 會將這次請求,轉 交給對應的 function 函數進行處理。
模塊化路由
為了方便對路由進行模塊化的管理,Express 不建議將路由直接掛載到 app 上,而是推薦將路由抽離為單獨的模塊。 將路由抽離為單獨模塊的步驟如下:
-
創建路由模塊對應的 .js 文件
- 創建router/login.js 存放 登錄、注冊、驗證碼三個路由
- 創建router/heroes.js 存放 和英雄相關的所有路由
-
調用 express.Router() 函數創建路由對象
const express = require('express'); const router = express.Router();
-
向路由對象上掛載具體的路由
// 把app換成router,比如 router.get('/xxx/xxx', (req, res) => {}); router.post('/xxx/xxx', (req, res) => {});
-
使用 module.exports 向外共享路由對象
module.exports = router;
-
使用 app.use() 函數注冊路由模塊 – app.js
// app.js 中,將路由導入,注冊成中間件 const login = require('./router/logon.js'); app.use(login)// app.use(require('./router/heroes.js')); app.use( require(path.join(__dirname, 'router', 'heores.js')) );
為路由模塊添加前綴
我們可以省略路由模塊中的 /api
前綴,而是在注冊中間件的時候,統一設置。
app.use('/api', router);
具體:
app.js中:
// 導入路由模塊,并注冊成中間件
app.use('/api', require(path.join(__dirname, 'router', 'login.js')) );
app.use('/my', require(path.join(__dirname, 'router', 'heroes.js')) );
路由文件中,把前綴 /api 和 /my 去掉
使用路由模塊的好處
- 分模塊管理路徑,提高了代碼的可讀性
- 可維護性更強
- 減少路由的匹配次數
- 權限管理更方便
- etc…