您好,登錄后才能下訂單哦!
這篇文章主要介紹“如何理解JavaScript 異步編程”,在日常操作中,相信很多人在如何理解JavaScript 異步編程問題上存在疑惑,小編查閱了各式資料,整理出簡單好用的操作方法,希望對大家解答”如何理解JavaScript 異步編程”的疑惑有所幫助!接下來,請跟著小編一起來學習吧!
“Give me a promise, I will not go anywhere, just stand here and wait for you.”
“給我一個承諾,我哪里都不會去,就在原地等你。” 這句話形式 Promise 還挺有意思的,文中我會再提及!
隨著 ES6 標準的出現,給我們帶來了一個新的異步解決方案 Promise。目前絕大多數 JavaScript 新增的異步 API 無論是在瀏覽器端還是 Node.js 服務端都是基于 Promise 構建的,以前基于 Callback 形式的也有解決方案將其轉為 Promise。
看過筆者上一節對 “Promise 前世 Deferred 的講解”,對于本章學習相對會更輕松一些,開始前我們還是先了解下 Promise/A+ 規范,更好的理解下 Promise 的一些思想和規則。
Promise 是一個對象用來表示異步操作的結果,我們沒有辦法同步的知道它的結果,但是這個結果可以用來表示未來值,將來的某個時間點我們可以拿到該值,它可能成功,也可能失敗,也會一直等待下去(這個請看下文 “無法取消的承諾”)。
在 Promise A+ 規范中有一些專業的術語,先了解下:
fulfill:Promise 在成功時的一個結果,表示解決,在很多的 Promise 實現中會使用 resolve 代替,這是一個意思,通常在 resolve 里我們接收程序的正確響應。
reject:Promise 在失敗時的一個結果,通常在 reject 里我們接收一個錯誤信息。
eventual value:代表終值,這是 Promise 被解決時傳遞給解決回調的值,例如 resolve(value) 這時 Promise 狀態就會結束進入 fulfill。
reason:拒因,指 Promise 在被拒絕時傳遞給拒絕回調的值,例如 reject(reason) 這時 Promise 狀態結束進入 reject。
這些概念在我們想要更深入的了解 Promise 是需要的,例如,我們實現一個自己的 Promise 最好也是按照這種規范去做,本節我們主要將 Promise 的基礎使用,后面也會在異步進階里去講 Promise 的實現原理,實現一個自己的 Promise 對象。
一個 Promise 在被創建出來時是一個等待態,最后要么成功、要么失敗這個狀態是不能夠逆轉的:
等待態(Pending)
執行態(Fulfilled)
拒絕態(Rejected)
這幾個狀態流轉就像一個狀態機,通過這個圖也可看到狀態只能從 Pending -> Fulfilled 或 Pending -> Rejected
目前有些 API 直接是基于 Promise 的形式,例如 Fetch API 從網絡獲取數據。
fetch('http://example.com/movies.json') .then(function(response) { // TODO });
舉一個 Node.js 的示例,例如 fs.readFile() 這個 API 默認是 callback 的形式,如果要轉為 Promise 也很方便。
fs.readFile('xxx', function(err, result) { console.error('Error: ', err); console.log('Result: ', result); });
方式一:new Promise 實現
new Promise() 是創建一個新的 Promise 對象,之后我們可以在里面使用 resolve、reject 返回結果信息。
const readFile = filename => new Promise((resolve, reject) => { fs.readFile(filename, (err, file) => { if (err) { reject(err); } else { resolve(file); } }) }); readFile('xxx') .then(result => console.log(result)) .catch(err => console.log(err));
方式二:Node.js util.promisify 工具
Node.js util 模塊提供了很多工具函數。為了解決回調地獄問題,Nodejs v8.0.0 提供了 promisify 方法可以將 Callback 轉為 Promise 對象。筆者之前也曾寫過一篇解析 “Node.js 源碼解析 util.promisify 如何將 Callback 轉為 Promise”
const { promisify } = require('util'); const readFilePromisify = util.promisify(fs.readFile); // 轉為 Promise readFilePromisify('xxx') .then(result => console.log(result)) .catch(err => console.log(err));
除此之外 Node.js fs 模塊的 fs.promises API 提供了一組備用的異步文件系統的方法,它們返回 Promise 對象而不是使用回調。API 可通過 require('fs').promises 或 require('fs/promises') 訪問。
const fsPromises = require('fs/promises'); fsPromises('xxx') .then(result => console.log(result)) .catch(err => console.log(err));
Promise 實例提供了兩種錯誤捕獲的方式:一是 Promise.then() 方法傳入第二個參數,另一種是 Promise 實例的 catch() 方法。
.then() 第二個回調參數捕獲錯誤具有就近的原則,不會影響后續 then 的進行。
Promise 拋錯具有冒泡機制,能夠不斷傳遞,可以使用 catch() 統一處理。
const ajax = function(){ console.log('promise 開始執行'); return new Promise(function(resolve,reject){ setTimeout(function(){ reject(`There's a mistake`); },1000); }); } ajax() .then(function(){ console.log('then1'); return Promise.resolve(); }, err => { console.log('then1里面捕獲的err: ', err); }) .then(function(){ console.log('then2'); return Promise.reject(`There's a then mistake`); }) .catch(err => { console.log('catch里面捕獲的err: ', err); }); // 輸出 // promise開始執行 // then1里面捕獲的err: There's a mistake // then2 // catch里面捕獲的err: There's a then mistake
Promise.all() 并行執行
Promise.all() 以數組的形式接收多個 Promise 實例,內部好比一個 for 循環執行傳入的多個 Promise 實例,當所有結果都成功之后返回結果,執行過程中一旦其中某個 Promise 實例發生 reject 就會觸發 Promise.all() 的 catch() 函數。以下示例,加載 3 張圖片,如果全部成功之后渲染結果到頁面中。
function loadImg(src){ return new Promise((resolve,reject) => { let img = document.createElement('img'); img.src = src; img.onload = () => { resolve(img); } img.onerror = (err) => { reject(err) } }) } function showImgs(imgs){ imgs.forEach(function(img){ document.body.appendChild(img) }) } Promise.all([ loadImg('http://www.xxxxxx.com/uploads/1.jpg'), loadImg('http://www.xxxxxx.com/uploads/2.jpg'), loadImg('http://www.xxxxxx.com/uploads/3.jpg') ]).then(showImgs)
在 Promise 鏈式調用中,任意時刻都只有一個任務執行,下一個任務要等待這個任務完成之后才能執行,如果現在我有兩個或以上的任務,之間沒有順序依賴關系,希望它們能夠并行執行,這樣可以提高效率,此時就可以選擇 Promise.all()。
Promise.race() 率先執行
Promise.race() 只要其中一個 Promise 實例率先發生改變,其它的將不在響應。好比短跑比賽,我只想知道第一是誰,當第一個人跨越終點線之后,我的目的就達到了。還是基于上面的示例,只要有一個圖片加載完成就直接添加到頁面。
function showImgs(img){ let p = document.createElement('p'); p.appendChild(img); document.body.appendChild(p); } Promise.race([ loadImg('http://www.xxxxxx.com/uploads/1.jpg'), loadImg('http://www.xxxxxx.com/uploads/2.jpg'), loadImg('http://www.xxxxxx.com/uploads/3.jpg') ]).then(showImgs)
Promise.finally()
Promise 在執行后最終結果要么成功進入 then,要么失敗 進入 catch。也許某些時候我們需要一個總是能夠被調用的回調,以便做一些清理工作,ES7 新加入了 finally 也許是你不錯的選擇。
Promise.finally() 在 Node.js 10.3.0 版本之后支持。
const p = Promise.resolve(); p .then(onSuccess) .catch(onFailed) .finally(cleanup);
Promise.any()
Promise.any() 接收一個數組作為參數,可傳入多個 Promise 實例,只要其中一個 Promise 變為 Fulfilled 狀態,就返回該 Promise 實例,只有全部 Promise 實例都變為 Rejected 拒絕態,Promise.any() 才會返回 Rejected。Promise.any() 在 Node.js 15.14.0 版本之后支持。
Promise.any([ Promise.reject('FAILED'), Promise.resolve('SUCCESS1'), Promise.resolve('SUCCESS2'), ]).then(result => { console.log(result); // SUCCESS1 });
Promise.allSettled()
Promise.allSettled() 與 Promise.all() 類似,不同的是 Promise.allSettled() 執行完成不會失敗,它會將所有的結果以數組的形式返回,我們可以拿到每個 Promise 實例的執行狀態和結果。
Promise.allSettled() 在 Node.js 12.10.0 版本之后支持。
Promise.allSettled([ Promise.reject('FAILED'), Promise.resolve('SUCCESS1'), Promise.resolve('SUCCESS2'), ]).then(results => { console.log(results); }); // [ // { status: 'rejected', reason: 'FAILED' }, // { status: 'fulfilled', value: 'SUCCESS1' }, // { status: 'fulfilled', value: 'SUCCESS2' } // ]
剛開始引用了一句話 “Give me a promise, I will not go anywhere, just stand here and wait for you.” 就好比一個小伙子對一個心儀的姑娘說:“給我一個承諾,我哪里都不會去,就在原地等你”。
好比我們的程序,創建了一個 Promise 對象 promise,并為其注冊了完成和拒絕的處理函數,因為一些原因,我們沒有給予它 resolve/reject,這個時候 promise 對象將會一直處于 Pending 等待狀態。我們也無法從外部取消。如果 then 后面還有業務需要處理,也將會一直等待下去,當我們自己去包裝一個 Promise 對象時要盡可能的避免這種情況發生。
const promise = new Promise((resolve, reject) => { // 沒有 resolve 也沒有 reject }); console.log(promise); // Promise {<pending>} promise .then(() => { console.log('resolve'); }).catch(err => { console.log('reject'); });
這是我們之前在講解 JavaScript 異步編程指南 Callback 一節寫的例子:
fs.readdir('/path/xxxx', (err, files) => { if (err) { // TODO... } files.forEach((filename, index) => { fs.lstat(filename, (err, stats) => { if (err) { // TODO... } if (stats.isFile()) { fs.readFile(filename, (err, file) => { // TODO }) } }) }) });
Node.js 的 fs 模塊為我們提供了 promises 對象,現在解決了深層次嵌套問題,這個問題還有更優雅的寫法,在之后的 Async/Await 章節我們會繼續介紹。
const fs = require('fs').promises; const path = require('path'); const rootDir = '/path/xxxx'; fs.readdir('/path/xxxx') .then(files => { files.forEach(checkFileAndRead); }) .catch(err => { // TODO }); function checkFileAndRead(filename, index) { const file = path.resolve(rootDir, filename); return fs.lstat(file) .then(stats => { if (stats.isFile()) { return fs.readFile(file); } }) .then(chunk => { // TODO }) .catch(err => { // TODO }) }
Promise 是很好的,它解決了 callback 形式的回調地獄、難以管理的錯誤處理問題, Promise 提供了一種鏈式的以線性的方式(.then().then().then()...)來管理我們的異步代碼,這種方式是可以的,解決了我們一些問題,但是并非完美,在 Async/Await 章節你會看到關于異步編程問題更好的解決方案,但是 Promise 是基礎,請掌握它。
到此,關于“如何理解JavaScript 異步編程”的學習就結束了,希望能夠解決大家的疑惑。理論與實踐的搭配能更好的幫助大家學習,快去試試吧!若想繼續學習更多相關知識,請繼續關注億速云網站,小編會繼續努力為大家帶來更多實用的文章!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。