您好,登錄后才能下訂單哦!
nodejs中的異步編程的作用有哪些?相信很多沒有經驗的人對此束手無策,為此本文總結了問題出現的原因和解決方法,通過這篇文章希望你能解決這個問題。
同步異步和阻塞非阻塞
在討論nodejs的異步編程之前,讓我們來討論一個比較容易混淆的概念,那就是同步,異步,阻塞和非阻塞。
所謂阻塞和非阻塞是指進程或者線程在進行操作或者數據讀寫的時候,是否需要等待,在等待的過程中能否進行其他的操作。
如果需要等待,并且等待過程中線程或進程無法進行其他操作,只能傻傻的等待,那么我們就說這個操作是阻塞的。
反之,如果進程或者線程在進行操作或者數據讀寫的過程中,還可以進行其他的操作,那么我們就說這個操作是非阻塞的。
同步和異步,是指訪問數據的方式,同步是指需要主動讀取數據,這個讀取過程可能是阻塞或者是非阻塞的。而異步是指并不需要主動去讀取數據,是被動的通知。
很明顯,javascript中的回調是一個被動的通知,我們可以稱之為異步調用。
javascript中的回調是異步編程的一個非常典型的例子:
document.getElementById('button').addEventListener('click', () => { console.log('button clicked!'); })
上面的代碼中,我們為button添加了一個click事件監聽器,如果監聽到了click事件,則會出發回調函數,輸出相應的信息。
回調函數就是一個普通的函數,只不過它被作為參數傳遞給了addEventListener,并且只有事件觸發的時候才會被調用。
上篇文章我們講到的setTimeout和setInterval實際上都是異步的回調函數。
在nodejs中怎么處理回調的錯誤信息呢?nodejs采用了一個非常巧妙的辦法,在nodejs中,任何回調函數中的第一個參數為錯誤對象,我們可以通過判斷這個錯誤對象的存在與否,來進行相應的錯誤處理。
fs.readFile('/文件.json', (err, data) => { if (err !== null) { //處理錯誤 console.log(err) return } //沒有錯誤,則處理數據。 console.log(data) })
javascript的回調雖然非常的優秀,它有效的解決了同步處理的問題。但是遺憾的是,如果我們需要依賴回調函數的返回值來進行下一步的操作的時候,就會陷入這個回調地獄。
叫回調地獄有點夸張了,但是也是從一方面反映了回調函數所存在的問題。
fs.readFile('/a.json', (err, data) => { if (err !== null) { fs.readFile('/b.json',(err,data) =>{ //callback inside callback }) } })
怎么解決呢?
別怕ES6引入了Promise,ES2017引入了Async/Await都可以解決這個問題。
Promise 是異步編程的一種解決方案,比傳統的解決方案“回調函數和事件”更合理和更強大。
所謂Promise,簡單說就是一個容器,里面保存著某個未來才會結束的事件(通常是一個異步操作)的結果。
從語法上說,Promise 是一個對象,從它可以獲取異步操作的消息。
Promise有兩個特點:
對象的狀態不受外界影響。
Promise對象代表一個異步操作,有三種狀態:Pending(進行中)、Resolved(已完成,又稱 Fulfilled)和Rejected(已失敗)。
只有異步操作的結果,可以決定當前是哪一種狀態,任何其他操作都無法改變這個狀態。
一旦狀態改變,就不會再變,任何時候都可以得到這個結果。
Promise對象的狀態改變,只有兩種可能:從Pending變為Resolved和從Pending變為Rejected。
這與事件(Event)完全不同,事件的特點是,如果你錯過了它,再去監聽,是得不到結果的。
Promise將異步操作以同步操作的流程表達出來,避免了層層嵌套的回調函數。
Promise對象提供統一的接口,使得控制異步操作更加容易。
無法取消Promise,一旦新建它就會立即執行,無法中途取消。
如果不設置回調函數,Promise內部拋出的錯誤,不會反應到外部。
當處于Pending狀態時,無法得知目前進展到哪一個階段(剛剛開始還是即將完成)。
Promise對象是一個構造函數,用來生成Promise實例:
var promise = new Promise(function(resolve, reject) { // ... some code if (/* 異步操作成功 */){ resolve(value); } else { reject(error); } } );
promise可以接then操作,then操作可以接兩個function參數,第一個function的參數就是構建Promise的時候resolve的value,第二個function的參數就是構建Promise的reject的error。
promise.then(function(value) { // success }, function(error) { // failure } );
我們看一個具體的例子:
function timeout(ms){ return new Promise(((resolve, reject) => { setTimeout(resolve,ms,'done'); })) } timeout(100).then(value => console.log(value));
Promise中調用了一個setTimeout方法,并會定時觸發resolve方法,并傳入參數done。
最后程序輸出done。
Promise一經創建就會立馬執行。但是Promise.then中的方法,則會等到一個調用周期過后再次調用,我們看下面的例子:
let promise = new Promise(((resolve, reject) => { console.log('Step1'); resolve(); })); promise.then(() => { console.log('Step3'); }); console.log('Step2'); 輸出: Step1 Step2 Step3
Promise當然很好,我們將回調地獄轉換成了鏈式調用。我們用then來將多個Promise連接起來,前一個promise resolve的結果是下一個promise中then的參數。
鏈式調用有什么缺點呢?
比如我們從一個promise中,resolve了一個值,我們需要根據這個值來進行一些業務邏輯的處理。
假如這個業務邏輯很長,我們就需要在下一個then中寫很長的業務邏輯代碼。這樣讓我們的代碼看起來非常的冗余。
那么有沒有什么辦法可以直接返回promise中resolve的結果呢?
答案就是await。
當promise前面加上await的時候,調用的代碼就會停止直到 promise 被解決或被拒絕。
注意await一定要放在async函數中,我們來看一個async和await的例子:
const logAsync = () => { return new Promise(resolve => { setTimeout(() => resolve('小馬哥'), 5000) }) }
上面我們定義了一個logAsync函數,該函數返回一個Promise,因為該Promise內部使用了setTimeout來resolve,所以我們可以將其看成是異步的。
要是使用await得到resolve的值,我們需要將其放在一個async的函數中:
const doSomething = async () => { const resolveValue = await logAsync(); console.log(resolveValue); }
await實際上是去等待promise的resolve結果我們把上面的例子結合起來:
const logAsync = () => { return new Promise(resolve => { setTimeout(() => resolve('小馬哥'), 1000) }) } const doSomething = async () => { const resolveValue = await logAsync(); console.log(resolveValue); } console.log('before') doSomething(); console.log('after')
上面的例子輸出:
before
after
小馬哥
可以看到,aysnc是異步執行的,并且它的順序是在當前這個周期之后。
async會讓所有后面接的函數都變成Promise,即使后面的函數沒有顯示的返回Promise。
const asyncReturn = async () => { return 'async return' } asyncReturn().then(console.log)
因為只有Promise才能在后面接then,我們可以看出async將一個普通的函數封裝成了一個Promise:
const asyncReturn = async () => { return Promise.resolve('async return') } asyncReturn().then(console.log)
promise避免了回調地獄,它將callback inside callback改寫成了then的鏈式調用形式。
但是鏈式調用并不方便閱讀和調試。于是出現了async和await。
async和await將鏈式調用改成了類似程序順序執行的語法,從而更加方便理解和調試。
我們來看一個對比,先看下使用Promise的情況:
const getUserInfo = () => { return fetch('/users.json') // 獲取用戶列表 .then(response => response.json()) // 解析 JSON .then(users => users[0]) // 選擇第一個用戶 .then(user => fetch(`/users/${user.name}`)) // 獲取用戶數據 .then(userResponse => userResponse.json()) // 解析 JSON } getUserInfo()
將其改寫成async和await:
const getUserInfo = async () => { const response = await fetch('/users.json') // 獲取用戶列表 const users = await response.json() // 解析 JSON const user = users[0] // 選擇第一個用戶 const userResponse = await fetch(`/users/${user.name}`) // 獲取用戶數據 const userData = await userResponse.json() // 解析 JSON return userData } getUserInfo()
看完上述內容,你們掌握nodejs中的異步編程的作用有哪些的方法了嗎?如果還想學到更多技能或想了解更多相關內容,歡迎關注億速云行業資訊頻道,感謝各位的閱讀!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。