日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

深入理解nodejs中的异步编程

發(fā)布時間:2024/2/28 编程问答 30 豆豆
生活随笔 收集整理的這篇文章主要介紹了 深入理解nodejs中的异步编程 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

文章目錄

  • 簡介
  • 同步異步和阻塞非阻塞
  • javascript中的回調(diào)
    • 回調(diào)函數(shù)的錯誤處理
    • 回調(diào)地獄
  • ES6中的Promise
    • 什么是Promise
    • Promise的特點
    • Promise的優(yōu)點
    • Promise的缺點
    • Promise的用法
    • Promise的執(zhí)行順序
  • async和await
    • async的執(zhí)行順序
    • async的特點
  • 總結(jié)

簡介

因為javascript默認情況下是單線程的,這意味著代碼不能創(chuàng)建新的線程來并行執(zhí)行。但是對于最開始在瀏覽器中運行的javascript來說,單線程的同步執(zhí)行環(huán)境顯然無法滿足頁面點擊,鼠標移動這些響應(yīng)用戶的功能。于是瀏覽器實現(xiàn)了一組API,可以讓javascript以回調(diào)的方式來異步響應(yīng)頁面的請求事件。

更進一步,nodejs引入了非阻塞的 I/O ,從而將異步的概念擴展到了文件訪問、網(wǎng)絡(luò)調(diào)用等。

今天,我們將會深入的探討一下各種異步編程的優(yōu)缺點和發(fā)展趨勢。

同步異步和阻塞非阻塞

在討論nodejs的異步編程之前,讓我們來討論一個比較容易混淆的概念,那就是同步,異步,阻塞和非阻塞。

所謂阻塞和非阻塞是指進程或者線程在進行操作或者數(shù)據(jù)讀寫的時候,是否需要等待,在等待的過程中能否進行其他的操作。

如果需要等待,并且等待過程中線程或進程無法進行其他操作,只能傻傻的等待,那么我們就說這個操作是阻塞的。

反之,如果進程或者線程在進行操作或者數(shù)據(jù)讀寫的過程中,還可以進行其他的操作,那么我們就說這個操作是非阻塞的。

同步和異步,是指訪問數(shù)據(jù)的方式,同步是指需要主動讀取數(shù)據(jù),這個讀取過程可能是阻塞或者是非阻塞的。而異步是指并不需要主動去讀取數(shù)據(jù),是被動的通知。

很明顯,javascript中的回調(diào)是一個被動的通知,我們可以稱之為異步調(diào)用。

javascript中的回調(diào)

javascript中的回調(diào)是異步編程的一個非常典型的例子:

document.getElementById('button').addEventListener('click', () => {console.log('button clicked!'); })

上面的代碼中,我們?yōu)閎utton添加了一個click事件監(jiān)聽器,如果監(jiān)聽到了click事件,則會出發(fā)回調(diào)函數(shù),輸出相應(yīng)的信息。

回調(diào)函數(shù)就是一個普通的函數(shù),只不過它被作為參數(shù)傳遞給了addEventListener,并且只有事件觸發(fā)的時候才會被調(diào)用。

上篇文章我們講到的setTimeout和setInterval實際上都是異步的回調(diào)函數(shù)。

回調(diào)函數(shù)的錯誤處理

在nodejs中怎么處理回調(diào)的錯誤信息呢?nodejs采用了一個非常巧妙的辦法,在nodejs中,任何回調(diào)函數(shù)中的第一個參數(shù)為錯誤對象,我們可以通過判斷這個錯誤對象的存在與否,來進行相應(yīng)的錯誤處理。

fs.readFile('/文件.json', (err, data) => {if (err !== null) {//處理錯誤console.log(err)return}//沒有錯誤,則處理數(shù)據(jù)。console.log(data) })

回調(diào)地獄

javascript的回調(diào)雖然非常的優(yōu)秀,它有效的解決了同步處理的問題。但是遺憾的是,如果我們需要依賴回調(diào)函數(shù)的返回值來進行下一步的操作的時候,就會陷入這個回調(diào)地獄。

叫回調(diào)地獄有點夸張了,但是也是從一方面反映了回調(diào)函數(shù)所存在的問題。

fs.readFile('/a.json', (err, data) => {if (err !== null) {fs.readFile('/b.json',(err,data) =>{//callback inside callback})} })

怎么解決呢?

別怕ES6引入了Promise,ES2017引入了Async/Await都可以解決這個問題。

ES6中的Promise

什么是Promise

Promise 是異步編程的一種解決方案,比傳統(tǒng)的解決方案“回調(diào)函數(shù)和事件”更合理和更強大。

所謂Promise,簡單說就是一個容器,里面保存著某個未來才會結(jié)束的事件(通常是一個異步操作)的結(jié)果。

從語法上說,Promise 是一個對象,從它可以獲取異步操作的消息。

Promise的特點

Promise有兩個特點:

  • 對象的狀態(tài)不受外界影響。
  • Promise對象代表一個異步操作,有三種狀態(tài):Pending(進行中)、Resolved(已完成,又稱 Fulfilled)和Rejected(已失敗)。

    只有異步操作的結(jié)果,可以決定當(dāng)前是哪一種狀態(tài),任何其他操作都無法改變這個狀態(tài)。

  • 一旦狀態(tài)改變,就不會再變,任何時候都可以得到這個結(jié)果。
  • Promise對象的狀態(tài)改變,只有兩種可能:從Pending變?yōu)镽esolved和從Pending變?yōu)镽ejected。

    這與事件(Event)完全不同,事件的特點是,如果你錯過了它,再去監(jiān)聽,是得不到結(jié)果的。

    Promise的優(yōu)點

    Promise將異步操作以同步操作的流程表達出來,避免了層層嵌套的回調(diào)函數(shù)。

    Promise對象提供統(tǒng)一的接口,使得控制異步操作更加容易。

    Promise的缺點

  • 無法取消Promise,一旦新建它就會立即執(zhí)行,無法中途取消。

  • 如果不設(shè)置回調(diào)函數(shù),Promise內(nèi)部拋出的錯誤,不會反應(yīng)到外部。

  • 當(dāng)處于Pending狀態(tài)時,無法得知目前進展到哪一個階段(剛剛開始還是即將完成)。

  • Promise的用法

    Promise對象是一個構(gòu)造函數(shù),用來生成Promise實例:

    var promise = new Promise(function(resolve, reject) { // ... some code if (/* 異步操作成功 */){ resolve(value); } else { reject(error); } } );

    promise可以接then操作,then操作可以接兩個function參數(shù),第一個function的參數(shù)就是構(gòu)建Promise的時候resolve的value,第二個function的參數(shù)就是構(gòu)建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中調(diào)用了一個setTimeout方法,并會定時觸發(fā)resolve方法,并傳入?yún)?shù)done。

    最后程序輸出done。

    Promise的執(zhí)行順序

    Promise一經(jīng)創(chuàng)建就會立馬執(zhí)行。但是Promise.then中的方法,則會等到一個調(diào)用周期過后再次調(diào)用,我們看下面的例子:

    let promise = new Promise(((resolve, reject) => {console.log('Step1');resolve(); }));promise.then(() => {console.log('Step3'); });console.log('Step2');輸出: Step1 Step2 Step3

    async和await

    Promise當(dāng)然很好,我們將回調(diào)地獄轉(zhuǎn)換成了鏈式調(diào)用。我們用then來將多個Promise連接起來,前一個promise resolve的結(jié)果是下一個promise中then的參數(shù)。

    鏈式調(diào)用有什么缺點呢?

    比如我們從一個promise中,resolve了一個值,我們需要根據(jù)這個值來進行一些業(yè)務(wù)邏輯的處理。

    假如這個業(yè)務(wù)邏輯很長,我們就需要在下一個then中寫很長的業(yè)務(wù)邏輯代碼。這樣讓我們的代碼看起來非常的冗余。

    那么有沒有什么辦法可以直接返回promise中resolve的結(jié)果呢?

    答案就是await。

    當(dāng)promise前面加上await的時候,調(diào)用的代碼就會停止直到 promise 被解決或被拒絕。

    注意await一定要放在async函數(shù)中,我們來看一個async和await的例子:

    const logAsync = () => {return new Promise(resolve => {setTimeout(() => resolve('小馬哥'), 5000)}) }

    上面我們定義了一個logAsync函數(shù),該函數(shù)返回一個Promise,因為該Promise內(nèi)部使用了setTimeout來resolve,所以我們可以將其看成是異步的。

    要是使用await得到resolve的值,我們需要將其放在一個async的函數(shù)中:

    const doSomething = async () => {const resolveValue = await logAsync();console.log(resolveValue); }

    async的執(zhí)行順序

    await實際上是去等待promise的resolve結(jié)果我們把上面的例子結(jié)合起來:

    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是異步執(zhí)行的,并且它的順序是在當(dāng)前這個周期之后。

    async的特點

    async會讓所有后面接的函數(shù)都變成Promise,即使后面的函數(shù)沒有顯示的返回Promise。

    const asyncReturn = async () => {return 'async return' }asyncReturn().then(console.log)

    因為只有Promise才能在后面接then,我們可以看出async將一個普通的函數(shù)封裝成了一個Promise:

    const asyncReturn = async () => {return Promise.resolve('async return') }asyncReturn().then(console.log)

    總結(jié)

    promise避免了回調(diào)地獄,它將callback inside callback改寫成了then的鏈式調(diào)用形式。

    但是鏈式調(diào)用并不方便閱讀和調(diào)試。于是出現(xiàn)了async和await。

    async和await將鏈式調(diào)用改成了類似程序順序執(zhí)行的語法,從而更加方便理解和調(diào)試。

    我們來看一個對比,先看下使用Promise的情況:

    const getUserInfo = () => {return fetch('/users.json') // 獲取用戶列表.then(response => response.json()) // 解析 JSON.then(users => users[0]) // 選擇第一個用戶.then(user => fetch(`/users/${user.name}`)) // 獲取用戶數(shù)據(jù).then(userResponse => userResponse.json()) // 解析 JSON }getUserInfo()

    將其改寫成async和await:

    const getUserInfo = async () => {const response = await fetch('/users.json') // 獲取用戶列表const users = await response.json() // 解析 JSONconst user = users[0] // 選擇第一個用戶const userResponse = await fetch(`/users/${user.name}`) // 獲取用戶數(shù)據(jù)const userData = await userResponse.json() // 解析 JSONreturn userData }getUserInfo()

    可以看到業(yè)務(wù)邏輯變得更加清晰。同時,我們獲取到了很多中間值,這樣也方便我們進行調(diào)試。

    本文作者:flydean程序那些事

    本文鏈接:http://www.flydean.com/nodejs-async/

    本文來源:flydean的博客

    歡迎關(guān)注我的公眾號:「程序那些事」最通俗的解讀,最深刻的干貨,最簡潔的教程,眾多你不知道的小技巧等你來發(fā)現(xiàn)!

    總結(jié)

    以上是生活随笔為你收集整理的深入理解nodejs中的异步编程的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。