本篇文章給大家帶來了關于javascript的相關知識,其中主要整理了Promise的基本概念及使用方法的相關問題,包括了Promise基本概念、使用Promise解決回調地獄等等內容,下面一起來看一下,希望對大家有幫助。

一、前言
異步是為了提高CPU的占用率,讓其始終處于忙碌狀態。
有些操作(最典型的就是I/O)本身不需要CPU參與,而且非常耗時,如果不使用異步就會形成阻塞狀態,CPU空轉,頁面卡死。
在異步環境下發生I/O操作,CPU就把I/O工作扔一邊(此時I/O由其他控制器接手,仍然在數據傳輸),然后處理下一個任務,等I/O操作完成后通知CPU(回調就是一種通知方式)回來干活。
《JavaScript異步與回調》想要表達的核心內容是,異步工作的具體結束時間是不確定的,為了準確的在異步工作完成后進行后繼的處理,就需要向異步函數中傳入一個回調,從而在完成工作后繼續下面的任務。
雖然回調可以非常簡單的實現異步,但是卻會由于多重嵌套形成回調地獄。避免回調地獄就需要解嵌套,將嵌套編程改為線性編程。
Promise是JavaScript中處理回調地獄最優解法。
二、Promise基本概念
Promise可以翻譯為“承諾”,我們可以通過把異步工作封裝稱一個Promise,也就是做出一個承諾,承諾在異步工作結束后給出明確的信號!
Promise語法:
let promise = new Promise(function(resolve,reject){
// 異步工作})通過以上語法,我們就可以把異步工作封裝成一個Promise。在創建Promise時傳入的函數就是處理異步工作的方法,又被稱為executor(執行者)。
resolve和reject是由JavaScript自身提供的回調函數,當executor執行完了任務就可以調用:
resolve(result)——如果成功完成,并返回結果result;
reject(error)——如果執行是失敗并產生error;
executor會在Promise創建完成后立即自動執行,其執行狀態會改變Promise內部屬性的狀態:
state——最初是pending,然后在resolve被調用后轉為fulfilled,或者在reject被調用時變為rejected;
result——最初時undefined,然后在resolve(value)被調用后變為value,或者在reject被調用后變為error;
2.1 異步工作的封裝
文件模塊的fs.readFile就是一個異步函數,我們可以通過在executor中執行文件讀取操作,從而實現對異步工作的封裝。
以下代碼封裝了fs.readFile函數,并使用resolve(data)處理成功結果,使用reject(err)處理失敗的結果。
代碼如下:
let promise = new Promise((resolve, reject) => {
fs.readFile('1.txt', (err, data) => {
console.log('讀取1.txt')
if (err) reject(err)
resolve(data)
})
})如果我們執行這段代碼,就會輸出“讀取1.txt”字樣,證明在創建Promise后立刻就執行了文件讀取操作。
Promise內部封裝的通常都是異步代碼,但是并不是只能封裝異步代碼。
2.2 Promise執行結果獲取
以上Promise案例封裝了讀取文件操作,當完成創建后就會立即讀取文件。如果想要獲取Promise執行的結果,就需要使用then、catch和finally三個方法。
then
Promise的then方法可以用來處理Promise執行完成后的工作,它接收兩個回調參數,語法如下:
promise.then(function(result),function(error))
第一個回調函數用于處理成功執行后的結果,參數
result就是resolve接收的值;第二個回調函數用于處理失敗執行后的結果,參數
error就是reject接收的參數;
舉例:
let promise = new Promise((resolve, reject) => {
fs.readFile('1.txt', (err, data) => {
console.log('讀取1.txt')
if (err) reject(err)
resolve(data)
})
})
promise.then(
(data) => {
console.log('成功執行,結果是' + data.toString())
},
(err) => {
console.log('執行失敗,錯誤是' + err.message)
})如果文件讀取成功執行,會調用第一個函數:
PS E:\Code\Node\demos\03-callback> node .\index.js
讀取1.txt
成功執行,結果是1
刪掉1.txt,執行失敗,就會調用第二個函數:
PS E:\Code\Node\demos\03-callback> node .\index.js
讀取1.txt
執行失敗,錯誤是ENOENT: no such file or directory, open 'E:\Code\Node\demos\03-callback\1.txt'
如果我們只關注成功執行的結果,可以只傳入一個回調函數:
promise.then((data)=>{
console.log('成功執行,結果是' + data.toString())
})到這里我們就是實現了一次文件的異步讀取操作。
catch
如果我們只關注失敗的結果,可以把第一個then的回調傳null:promise.then(null,(err)=>{...})。
亦或者采用更優雅的方式:promise.catch((err)=>{...})
let promise = new Promise((resolve, reject) => {
fs.readFile('1.txt', (err, data) => {
console.log('讀取1.txt')
if (err) reject(err)
resolve(data)
})})promise.catch((err)=>{
console.log(err.message)
}).catch((err)=>{...})和then(null,(err)=>{...})作用完全相同。
finally
.finally是promise不論結果如何都會執行的函數,和try...catch...語法中的finally用途一樣,都可以處理和結果無關的操作。
例如:
new Promise((resolve,reject)=>{
//something...
}).finally(()=>{console.log('不論結果都要執行')}).then(result=>{...}, err=>{...})
finally回調沒有參數,不論成功與否都會執行
finally會傳遞promise的結果,所以在finally后仍然可以.then
三、使用Promise解決回調地獄
3.1 回調地獄出現的場景
現在,我們有一個需求:使用fs.readFile()方法順序讀取10個文件,并把十個文件的內容順序輸出。
由于fs.readFile()本身是異步的,我們必須使用回調嵌套的方式,代碼如下:
fs.readFile('1.txt', (err, data) => {
console.log(data.toString()) //1
fs.readFile('2.txt', (err, data) => {
console.log(data.toString())
fs.readFile('3.txt', (err, data) => {
console.log(data.toString())
fs.readFile('4.txt', (err, data) => {
console.log(data.toString())
fs.readFile('5.txt', (err, data) => {
console.log(data.toString())
fs.readFile('6.txt', (err, data) => {
console.log(data.toString())
fs.readFile('7.txt', (err, data) => {
console.log(data.toString())
fs.readFile('8.txt', (err, data) => {
console.log(data.toString())
fs.readFile('9.txt', (err, data) => {
console.log(data.toString())
fs.readFile('10.txt', (err, data) => {
console.log(data.toString())
// ==> 地獄之門
})
})
})
})
})
})
})
})
})
})雖然以上代碼能夠完成任務,但是隨著調用嵌套的增加,代碼層次變得更深,維護難度也隨之增加,尤其是我們使用的是可能包含了很多循環和條件語句的真實代碼,而不是例子中簡單的 console.log(...)。
3.2 不使用回調產生的后果
如果我們不使用回調,直接把fs.readFile()順序的按照如下代碼調用一遍,會發生什么呢?
//注意:這是錯誤的寫法
fs.readFile('1.txt', (err, data) => {
console.log(data.toString())
})
fs.readFile('2.txt', (err, data) => {
console.log(data.toString())
})
fs.readFile('3.txt', (err, data) => {
console.log(data.toString())
})
fs.readFile('4.txt', (err, data) => {
console.log(data.toString())
})
fs.readFile('5.txt', (err, data) => {
console.log(data.toString())
})
fs.readFile('6.txt', (err, data) => {
console.log(data.toString())
})
fs.readFile('7.txt', (err, data) => {
console.log(data.toString())
})
fs.readFile('8.txt', (err, data) => {
console.log(data.toString())
})
fs.readFile('9.txt', (err, data) => {
console.log(data.toString())
})
fs.readFile('10.txt', (err, data) => {
console.log(data.toString())
})以下是我測試的結果(每次執行的結果都是不一樣的):
PS E:\Code\Node\demos\03-callback> node .\index.js12346957108
產生這種非順序結果的原因是異步,并非多線程并行,異步在單線程里就可以實現。
之所以在這里使用這個錯誤的案例,是為了強調異步的概念,如果不理解為什么會產生這種結果,一定要回頭補課了!
3.3 Promise解決方案
使用Promise解決異步順序文件讀取的思路:
封裝一個文件讀取
promise1,并使用resolve返回結果使用
promise1.then接收并輸出文件讀取結果在
promise1.then中創建一個新的promise2對象,并返回調用新的
promise2.then接收并輸出讀取結果在
promise2.then中創建一個新的promise3對象,并返回調用新的
promise3.then接收并輸出讀取結果…
代碼如下:
let promise1 = new Promise((resolve, reject) => {
fs.readFile('1.txt', (err, data) => {
if (err) reject(err)
resolve(data)
})
})
let promise2 = promise1.then(
data => {
console.log(data.toString())
return new Promise((resolve, reject) => {
fs.readFile('2.txt', (err, data) => {
if (err) reject(err)
resolve(data)
})
})
}
)
let promise3 = promise2.then(
data => {
console.log(data.toString())
return new Promise((resolve, reject) => {
fs.readFile('3.txt', (err, data) => {
if (err) reject(err)
resolve(data)
})
})
}
)
let promise4 = promise3.then(
data => {
console.log(data.toString())
//.....
}
)
... ...這樣我們就把原本嵌套的回調地獄寫成了線性模式。
但是代碼還存在一個問題,雖然代碼從管理上變的美麗了,但是大大增加了代碼的長度。
3.4 鏈式編程
以上代碼過于冗長,我們可以通過兩個步驟,降低代碼量:
封裝功能重復的代碼,完成文件讀取和輸出工作
省略中間
promise的變量創建,將.then鏈接起來
代碼如下:
function myReadFile(path) {
return new Promise((resolve, reject) => {
fs.readFile(path, (err, data) => {
if (err) reject(err)
console.log(data.toString())
resolve()
})
})
}
myReadFile('1.txt')
.then(data => { return myReadFile('2.txt') })
.then(data => { return myReadFile('3.txt') })
.then(data => { return myReadFile('4.txt') })
.then(data => { return myReadFile('5.txt') })
.then(data => { return myReadFile('6.txt') })
.then(data => { return myReadFile('7.txt') })
.then(data => { return myReadFile('8.txt') })
.then(data => { return myReadFile('9.txt') })
.then(data => { return myReadFile('10.txt') })由于myReadFile方法會返回一個新的Promise,我們可以直接執行.then方法,這種編程方式被稱為鏈式編程。
代碼執行結果如下:
PS E:\Code\Node\demos\03-callback> node .\index.js12345678910
這樣就完成了異步且順序的文件讀取操作。
注意:在每一步的
.then方法中都必須返回一個新的Promise對象,否則接收到的將是上一個舊的Promise。這是因為每個
then方法都會把它的Promise繼續向下傳遞。






