一文帶你輕鬆掌握Promise

2023-02-10 22:00:18

前端js學習中,讓大家最難受的就是非同步的問題,解決非同步、回撥地獄等問題時你必須得學會promise,對於多數前端程式設計師來說promise簡直就是噩夢,本篇文章就是從通俗易懂的角度做為切入點,幫助大家輕鬆掌握promise

非同步程式設計


想要學習promise,你必須要懂得什麼是非同步程式設計!眾所周知,js語言是單執行緒機制。所謂單執行緒就是按次序執行,執行完一個任務再執行下一個。但是不影響存在同步非同步的兩種操作,這兩種操作做事情其實都是在一條流水線上(單執行緒),只是這兩種操作在單執行緒上的執行順序不一樣罷了!當js觸發到非同步任務時,會將非同步任務交給瀏覽器處理,當執行有結果時,會把非同步任務的回撥函數插入待處理佇列的隊尾!

我們通俗的去解釋一下我們的非同步:非同步就是從主執行緒發射一個子執行緒來完成任務,每一個任務有一個或多個回撥函數(callback),前一個任務結束後,不是執行後一個任務,而是執行回撥函數後一個任務則是不等前一個任務結束就執行,所以程式的執行順序與任務的排列順序是不一致的、非同步的.

在這裡插入圖片描述

該圖摘自於中的非同步程式設計小節,幫助大家更好的理解什麼是非同步!

回撥函數


回撥函數的定義非常簡單:一個函數被當做一個實參傳入到另一個函數(外部函數),並且這個函數在外部函數內被呼叫,用來完成某些任務的函數。就稱為回撥函數

回撥函數的兩種寫法(實現效果相同):

const text = () => {
	   document.write('hello james')
}
setTimeout(text,1000)
登入後複製
setTimeout(()=>{
	   document.write("hello james")
},1000)
登入後複製

這段程式碼中的 setTimeout 就是一個消耗時間較長的過程,它的第一個引數是個回撥函數,第二個引數是毫秒數,這個函數執行之後會產生一個子執行緒,子執行緒會等待 1 秒,然後執行回撥函數 "text",在文字中輸出hello james

setTimeout會在子執行緒中等待1秒,但是主執行緒的執行不會受到影響!例如以下程式碼:

setTimeout(()=>{
    document.write("hello davis")
},1000)
console.log('123456');
登入後複製

在這裡會先列印出來123456(主執行緒),然後一秒後在文字中輸出hello davis(子執行緒)

回撥地獄


回撥地獄這個詞聽起來就非常的高大上,想要接觸Promise之前,必須要懂得什麼是回撥地獄,以及為什麼會產生回撥地獄?
先來看看概念:當一個回撥函數巢狀一個回撥函數的時候就會出現一個巢狀結構當巢狀的多了就會出現回撥地獄的情況
舉個例子
比如我們傳送三個 ajax 請求:

  • 第一個正常傳送
  • 第二個請求需要第一個請求的結果中的某一個值作為引數
  • 第三個請求需要第二個請求的結果中的某一個值作為引數

你會看到以下程式碼

$.ajax({
  url: '我是第一個請求',
  type: 'get',
  success (res) {
    // 現在傳送第二個請求
    $.ajax({
      url: '我是第二個請求',
      type:'post',
      data: { a: res.a, b: res.b },
      success (res1) {
        // 進行第三個請求
        $.ajax({
          url: '我是第三個請求',
          type:'post',
          data: { a: res1.a, b: res1.b },
                  success (res2) { 
            console.log(res2) 
          }
        })
      }
    })
  }
})
登入後複製

這種程式碼看起來屬實是折磨人啊!當我們把程式碼寫成這樣的時候,就陷入了可維護性差的狀態了,程式碼體驗非常的不良好,看一會就給看懵了,為了解決這個問題,於是,就引入了我們的Promise,用Promise去解決回撥地獄問題!

Promise


Promise非同步程式設計的一種解決方案,比傳統的解決方案——回撥函數和事件——更合理和更強大,它是一個 ECMAScript 6 提供的類,目的是更加優雅地書寫複雜的非同步任務

Promise物件有以下兩個特點:

  • 物件的狀態不受外界影響。Promise物件代表一個非同步操作,有三種狀態:pending(進行中)、fulfilled(已成功)和rejected(已失敗)。只有非同步操作的結果,可以決定當前是哪一種狀態,任何其他操作都無法改變這個狀態。這也是Promise這個名字的由來,它的英語意思就是「承諾」,表示其他手段無法改變。

  • 一旦狀態改變,就不會再變,任何時候都可以得到這個結果。Promise物件的狀態改變,只有兩種可能:從pending變為fulfilled和從pending變為rejected。只要這兩種情況發生,狀態就凝固了,不會再變了,會一直保持這個結果,這時就稱為 resolved(已定型)。如果改變已經發生了,你再對Promise物件新增回撥函數,也會立即得到這個結果。這與事件(Event)完全不同,事件的特點是,如果你錯過了它,再去監聽,是得不到結果的。

兩個特點摘自於??

Promise語法格式

new Promise(function (resolve, reject) {
  // resolve 表示成功的回撥
  // reject 表示失敗的回撥
}).then(function (res) {
  // 成功的函數
}).catch(function (err) {
  // 失敗的函數
})
登入後複製

出現了new關鍵字,就明白了Promise物件其實就是一個建構函式,是用來生成Promise範例的。能看出來建構函式接收了一個函數作為引數,該函數就是Promise建構函式的回撥函數,該函數中有兩個引數resolvereject,這兩個引數也分別是兩個函數!

簡單的去理解的話resolve函數的目的是將Promise物件狀態變成成功狀態,在非同步操作成功時呼叫,將非同步操作的結果,作為引數傳遞出去。reject函數的目的是將Promise物件的狀態變成失敗狀態,在非同步操作失敗時呼叫,並將非同步操作報出的錯誤,作為引數傳遞出去。

Promise範例生成以後,可以用then方法分別指定resolved狀態rejected狀態的回撥函數。

程式碼範例:

        const promise = new Promise((resolve,reject)=>{
            //非同步程式碼
            setTimeout(()=>{
                // resolve(['111','222','333'])
                reject('error')
            },2000)
        })
        promise.then((res)=>{
            //兌現承諾,這個函數被執行
            console.log('success',res);
        }).catch((err)=>{
            //拒絕承諾,這個函數就會被執行
            console.log('fail',err);
        })
登入後複製

程式碼分析:

上邊說到Promise是一個建構函式,new之後等於說呼叫了建構函式,建構函式中傳的引數是一個函數,這個函數內的兩個引數分別又是兩個函數(reslovereject),雖然感覺很繞,但是理清思路會很清晰的!


我們得到物件promise,promise物件中自帶有兩個方法thencatch,這兩個方法中會分別再傳入一個回撥函數,這個回撥函數的目的在於返回你所需要成功或失敗的資訊!那麼怎麼去呼叫這兩個回撥函數呢?
看下方圖可以快速理解:

在這裡插入圖片描述
這兩個函數分別做為引數(reslovereject)傳到上方的函數中去了.隨後在建構函式的回撥函數中寫入非同步程式碼(例如:ajax定時器),這裡使用了定時器作為例子,如果你想表達的是成功回撥,你可以在內部呼叫函數reslove('一般情況下是後端返回的成功資料)。如果你想表達的是失敗回撥,你可以呼叫reject('一般情況下是後端返回的失敗資訊').

這些就是Promise執行的過程!雖然理解著很繞,但是多讀幾遍絕對有不一樣的收穫!

Promise鏈式

then方法返回的是一個新的Promise範例注意:不是原來那個Promise範例)。因此可以採用鏈式寫法,即then方法後面再呼叫另一個then方法

實際案例:
我想要實現在一個陣列中檢視一個貼文,但是我最終的目的是得到這個貼文下面的所有評論,這該怎麼實現呢?

實現思路
先從一個介面中獲取這個貼文的資訊,然後通過該貼文的貼文id從而獲取到該貼文下的所有評論

程式碼如下:

pajax({
    url:"http://localhost:3000/news",
    data : {
        author : "james"
    }
}).then(res=>{
    return pajax({
        url : "http://localhost:3000/comments",
        data : {
            newsId : res[0].id
        }
    })
}).then(res=>{
    console.log(res);
}).catch(err=>{
    console.log(err);
})
登入後複製

程式碼分析:

這裡使用了一個Promise已經封裝過的ajax,我們從第一個介面中得到了貼文id,然後在then中的函數傳送第二個請求(攜帶了第一個請求返回過來的引數),我們最後想要拿到第二個介面的結果,於是又有了一個then方法,但是在第一個then方法中要把一個新的Promise範例return出去,這樣的話,第二個then才起作用!(這是因為then方法是Promise 範例所具有的方法,也就是說,then方法是定義在原型物件Promise.prototype上的)====>我們可以列印一下:console.log(Promise.prototype)

在這裡插入圖片描述
可以看的出來原型物件Promise.prototype中是有then方法的!

Promise.all()

Promise.all()方法用於將多個 Promise 範例,包裝成一個新的 Promise 範例

語法格式:

const p = Promise.all([p1, p2, p3]);
登入後複製

Promise.all()方法接受一個陣列作為引數,p1、p2、p3都是 Promise 範例,如果不是,就會呼叫Promise.reslove() [該方法可自行了解]自動將引數轉為 Promise 範例,再進一步處理。

說那麼多白話沒用,我們可以根據一個案例,就可以明白Promise.all()的用途了。

實際案例:
如果你想實現一個效果:在一個頁面中,等到頁面中所有的請求返回資料後,再渲染頁面,該怎麼實現呢?(在實際開發中我們會看到loading載入頁面,等資料返回完後,loading載入頁面會消失,整個頁面就展現出來了,增強使用者的體驗。)

實現思路:
通過Promise.all()方法,等多個介面全部接收到資料後,再統一進行處理,然後渲染頁面

程式碼如下:

console.log("顯示載入中")
const q1 = pajax({
    url:"http://localhost:3000/looplist"
})

const q2 = pajax({
    url:"http://localhost:3000/datalist"
})
Promise.all([q1,q2]).then(res=>{
    console.log(res)
    console.log("隱藏載入中...")
}).catch(err=>{
    console.log(err)
})
登入後複製

程式碼分析:

在上方程式碼中,全域性列印顯示載入中是代替loading的頁面,表示該頁面現在正是loading頁面中,等到q1q2所請求介面都得到返回的資訊後,在then方法中接收收據,並且可以進行渲染頁面,同時隱藏了loading載入頁面!

小結

不論是在前端的專案開發中還是在前端的面試過程中,Promise的地位就是舉足輕重的,雖然解決非同步程式設計的終極解決方案是async和await,但是它們也是基於Promise封裝而來的,在以往文章中,我就說過,學習程式設計重要的是搞懂某個技術是怎麼實現的,而不是做一個cv俠,多去思考,才能進步。繼續加油吧,少年!

【相關推薦:、】

以上就是一文帶你輕鬆掌握Promise的詳細內容,更多請關注TW511.COM其它相關文章!