[JavaScript] Promise 與 Ajax/Axios

蘇水軒發表於2019-04-02

updateTime: 2019-4-5 23:00 

updateContent: async在專案中的使用簡談

前言

在單執行緒的js執行中,必然需要非同步的出現來協程操作,否則就不用玩了, 而在js非同步程式設計方案中,我們經歷了回撥地獄後終於推出了更合理強大的新方案,那就是——Promise,而在經歷了co模組的洗禮後,es7順利推出了Generator 的語法糖——Async(誰用誰知道,真滴爽=-=)

Promise 承諾

What

用於處理非同步回撥的一種解決方案,比傳統回撥更強大合理

WhyThisName

根據promise物件的第一特徵,除了非同步操作可以影響其狀態,其他外界操作都不能影響,表示一種承諾,即其他手段無法改變。

兩個特徵:

1. 物件狀態不受外界影響,三種狀態為: pengding, fulfilled(已成功), rejected(已失敗)
2. 狀態改變後不在改變,只有兩種改變: pending->fulfilled / pending->rejected

三個缺點: 

1. new後無法取消
2. 無回撥則內部錯誤無法丟擲

3. 當處於pending狀態時,無法得知目前進展到哪一個階段(剛剛開始還是即將完成)

How to use 

const promise = new Promise((resolve, reject) => {
  if (非同步操作完畢) {        
      resolve(val)      
  } else {       
      reject(error)      
  }
 })  
 promise.then((val) => { 
     // doSuccess  
 }, (err) => {      // doFailure    })複製程式碼


基於Promise的Ajax,告別回撥地獄

手打一時爽..

// 手寫一個ajax by promise 
function _ajaxGet (url, params) {        const promise =  new Promise(function(resolve, reject){            const handle = function () {                if (this.readyState == 4) {                    if (this.status === 200 || this.status === 304) {                        resolve(this.response)                                                             } else {                        reject(new Error(this.statusText))                                            }                }                            }            const XHR = XMLHttpRequest ? new XMLHttpRequest() : new window.ActiveXObject('Microsoft.XMLHTTP')            params = params || ''                  XHR.open('GET', url + params, true)            XHR.onreadystatechange = handle            XHR.responseType = 'json'            XHR.setRequestHeader('Accept', 'application/json')            XHR.send()        })        return promise    }    
function _ajaxPost (url, data) {        const promise =  new Promise(function(resolve, reject){            const handle = function () {                if (this.readyState == 4) {                    if (this.status === 200 || this.status === 304) {                        resolve(this.response)                                                                  } else {                        reject(new Error(this.statusText))                                            }                }                            }            const XHR = XMLHttpRequest ? new XMLHttpRequest() : new window.ActiveXObject('Microsoft.XMLHTTP')                          XHR.open('POST', url, true)            XHR.onreadystatechange = handle            XHR.responseType = 'json'            XHR.setRequestHeader('Content-Type', '"application/json')            XHR.send(data)                    })        return promise    }    
_ajaxGet('test.json').then(res => {        console.log(res)    }).catch(err => {                console.log(err)    })    
_ajaxPost('test.json', {id: 123})複製程式碼


其實把上面的程式碼封裝封裝加一些其他的配置引數,就是一個簡單的axios了(?我自己以為的),不過axios的原理就是通過promise實現的就沒錯了。

可以發現區別於傳統的回撥裡面寫非同步,回撥一層又一層,真的要好很多。

快速認識Promise物件的一些方法

1. Promise.all([p1,p2,p3])

類似於串聯電路,

陣列中的所有promise例項狀態都為已定型(resolve)時,則返回一個所有例項的返回值陣列進行該promise之後的回撥,

若有一個/多個為reject則返回第一個reject的promise的error

demo(來自es6阮老師書中demo)

// 生成一個Promise物件的陣列
const promises = [2, 3, 5, 7, 11, 13].map(function (id) {
  return getJSON('/post/' + id + ".json");
});

Promise.all(promises).then(function (posts) {
  // ...
}).catch(function(reason){
  // ...
});複製程式碼

2. Promise.race([p1,p2,p3])

類似於並聯電路,

返回多個例項中第一個進行了狀態變換的例項的返回值作為回撥函式的引數

3. 後續補充...

Promise內部一探

雖然沒看原始碼,但猜測是用觀察者模式實現的,通過改變狀態值來觸發對應回撥函式(根據其特性加一些別的引數設定),後面出一期觀察者模式與promise。

Promise的缺陷

假設有這樣一個場景,五個動畫,順序載入到一個dom上,要求五秒內完成,否則提示報錯並關閉動畫。好了這個場景先不去實現,可想而知,為了實現第一個順序載入,你需要

.then(res => {//anim1(); resolve()})

.then(res => {//anim2(); resolve()})

.then(res => {//anim3(); resolve()})

.then(res => {//anim4(); resolve()})

.then(res => {//anim5()})

程式碼越寫越長,橫縱變胖,賊難看,並且不容易察看

這個時候

Async Await(Generaor yiled)閃亮登場

今天先寫到這。。。累,明天接著寫

下面是目錄

Generator 與 執行器 與 Thunk(攜程函式)

Generator函式 function* helloworld () {}  利用Generator可以實現js的狀態機

是不是很像c中的指標, 沒錯Generator函式呼叫後不會執行(交出函式執行權),但是會返回一個遍歷器物件,通過對該物件的next(繼續)方法呼叫,對函式內程式碼進行執行,每次next執行會在遇到的第一個yield(暫停)停下,然後返回一個鍵值分別為value,done的物件來表示當前暫停右側表示式的值和遍歷的狀態。在多工情況下,多個Generator(async:我是誰我在那)通過yield(await:我是誰我在哪,並且我遇到的不是非同步我會立即執行再返回一個已定型的promise物件)來交換控制權。

一直執行繼續(next)然後當遍歷狀態(done)為true時遍歷結束,下次再執行就會一直返回

{value: undefined, done: true}

function* helloWorldGenerator() {
  yield 'hello';
  yield 'world';
  return 'ending';
}

var hw = helloWorldGenerator();
hw.next()
// { value: 'hello', done: false }

hw.next()
// { value: 'world', done: false }

hw.next()
// { value: 'ending', done: true }

hw.next()
// { value: undefined, done: true }
複製程式碼

程式碼終於看起來比回撥清晰了,但是流程卻有些不如意,那麼如何自動執行呢,有請

Thunk 傳名呼叫的一種實現策略

what: 計算機語言求值策略中的傳名呼叫,即將表示式直接傳入,減少不必要計算損失。編譯器的“傳名呼叫”實現,往往是將引數放到一個臨時函式之中,再將這個臨時函式傳入函式體。這個臨時函式就叫做 Thunk 函式。

// 正常版本的readFile(多引數版本)
fs.readFile(fileName, callback);

// Thunk版本的readFile(單引數版本)
var Thunk = function (fileName) {
  return function (callback) {
    return fs.readFile(fileName, callback);
  };
};

var readFileThunk = Thunk(fileName); 複製程式碼

readFileThunk(callback); 

當然js是值傳遞呼叫!在js中Thunk起到的主要作用是多參變單參後將回撥傳回。上面看到將callback傳回。

而在這裡對Genrator的自動流程管理實現的幫助中Thunk起到的是對next指標傳出後再傳入上次yield表示式執行完畢地址,這樣就可以可以把執行權在next傳出後又重新傳入,拿回執行權。

一句話,上次非同步成功後自動繼續yield後程式碼。

但是看起來好麻煩,還不如寫寫thenthenthen(Promise自動執行,變胖就變胖,口亨)是吧,還要寫Genrator的執行器。這個時候Co閃亮登場。

Co 小輕美

var co = require('co');
co(gen);
// Generator函式傳入co中自動執行複製程式碼

what: Thunk與Promise的結合糖

tip: 注意了co的實參只能是Thunk 函式或 Promise 物件。支援併發。

Async Await的優點

  • 內建執行器
  • 更好的語義
  • 更廣的適用性
  • 返回值是 Promise

例項Demo與專案中應用簡談

最近使用vue做了一個專案類似於阿里的iconFont,首先用一個很簡單的首頁狀態邏輯來介紹async的使用(順序載入)

1. 當進入首頁元件前在app元件created中首先呼叫initUserInfo非同步函式,該函式在store的index.js的dispatch中定義

2. 然後我們需要在首頁元件載入時呼叫幾個請求來渲染對應使用者的資料,這幾個請求需要使用userid這個資料

如果用promise來實現的話,那就是以下程式碼

methods: {
initUserInfo () {
    return _axios.get(uri).then(res => { // 參考上面用promise實現的ajax.getJson
        commit('userInfo', res.data.data) // 全域性狀態更新處理        
    })
}
initMyproInfo () {
    // ...
    return _axios.post(getProUri, {usrId: this.userInfo.id}) // ...mapGetters(['usrInfo'])
}
,
created () {
     this.initUserInfo().then(res => {
        console.log(res) // 列印獲取到的使用者資料
        return this.initMyproInfo()
    }).then(res => { // 這個回撥依賴於呼叫他的promise物件狀態
        this.this._message.info(`data id is ${data.id}`)
    }).catch(err => { // 捕捉then中第一個錯誤
        console.log(err)
    }) 
}
複製程式碼

如果用async 來實現的話

//...mapActions(['initUserInfo'])
async initUserInfo ({commit}) {
    await let {data} = _axios.get(uri)
    commit('initUserInfo', data.data) // 全域性狀態更新處理
    return data.data            
}
async created () { // 是不是看起來清爽多了!
    await this.initUserInfo()
    let data = await this.initMyproInfo() // 正常結果下,await後是一個promise物件,返回該物件的結果,在本例中即返回pro請求後的結果res
    this._message.info(`data id is ${data.id}`)
}複製程式碼




Last, 各位如果覺得還喜歡,對你理解有用,麻煩點個贊吧(給您鼓掌了!^_^)

如果有錯誤的地方,請您不吝指出,謝謝!


相關文章