您好,登錄后才能下訂單哦!
本篇內容主要講解“JavaScript中Promise的基本概念及使用方法是什么”,感興趣的朋友不妨來看看。本文介紹的方法操作簡單快捷,實用性強。下面就讓小編來帶大家學習“JavaScript中Promise的基本概念及使用方法是什么”吧!
異步是為了提高CPU的占用率,讓其始終處于忙碌狀態。
有些操作(最典型的就是I/O)本身不需要CPU參與,而且非常耗時,如果不使用異步就會形成阻塞狀態,CPU空轉,頁面卡死。
在異步環境下發生I/O操作,CPU就把I/O工作扔一邊(此時I/O由其他控制器接手,仍然在數據傳輸),然后處理下一個任務,等I/O操作完成后通知CPU(回調就是一種通知方式)回來干活。
《JavaScript異步與回調》想要表達的核心內容是,異步工作的具體結束時間是不確定的,為了準確的在異步工作完成后進行后繼的處理,就需要向異步函數中傳入一個回調,從而在完成工作后繼續下面的任務。
雖然回調可以非常簡單的實現異步,但是卻會由于多重嵌套形成回調地獄。避免回調地獄就需要解嵌套,將嵌套編程改為線性編程。
Promise
是JavaScript
中處理回調地獄最優解法。
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
;
文件模塊的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
內部封裝的通常都是異步代碼,但是并不是只能封裝異步代碼。
以上Promise
案例封裝了讀取文件操作,當完成創建后就會立即讀取文件。如果想要獲取Promise
執行的結果,就需要使用then
、catch
和finally
三個方法。
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())})
到這里我們就是實現了一次文件的異步讀取操作。
如果我們只關注失敗的結果,可以把第一個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
是promise
不論結果如何都會執行的函數,和try...catch...
語法中的finally
用途一樣,都可以處理和結果無關的操作。
例如:
new Promise((resolve,reject)=>{ //something...}).finally(()=>{console.log('不論結果都要執行')}).then(result=>{...}, err=>{...})
finally
回調沒有參數,不論成功與否都會執行
finally
會傳遞promise
的結果,所以在finally
后仍然可以.then
現在,我們有一個需求:使用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(...)
。
如果我們不使用回調,直接把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
產生這種非順序結果的原因是異步,并非多線程并行,異步在單線程里就可以實現。
之所以在這里使用這個錯誤的案例,是為了強調異步的概念,如果不理解為什么會產生這種結果,一定要回頭補課了!
使用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()) //..... })... ...
這樣我們就把原本嵌套的回調地獄寫成了線性模式。
但是代碼還存在一個問題,雖然代碼從管理上變的美麗了,但是大大增加了代碼的長度。
以上代碼過于冗長,我們可以通過兩個步驟,降低代碼量:
封裝功能重復的代碼,完成文件讀取和輸出工作
省略中間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
繼續向下傳遞。
到此,相信大家對“JavaScript中Promise的基本概念及使用方法是什么”有了更深的了解,不妨來實際操作一番吧!這里是億速云網站,更多相關內容可以進入相關頻道進行查詢,關注我們,繼續學習!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。