1. 程式人生 > 其它 >非同步程式設計 -- Promise

非同步程式設計 -- Promise

Promise

為了解決回撥函式經常出現的回撥地獄問題,CommonJS社群提出了Promise的規範,最終在es2015中被標準化,成為語言規範

Promise用來表示一個非同步任務最終是成功還是失敗,像是對任務作出的承諾,許下這個承諾後,任務進入到pending狀態,等待承諾的兌現,然後等承諾兌現後,狀態會根據結果發生改變,可能是成功,也可能是失敗,但是不再可能是pending等待狀態。當狀態發生發往後,會執行相應狀態的回撥函式。

Promise怎麼使用?

這裡簡單用Promise模擬一下ajax

// Promise 方式的ajax
function ajax(url){
  return new Promise(function(resolve,reject){
    var xhr=new XMLHttpRequest()
    xhr.open('GET',url)
    xhr.responseType='json'
    xhr.onload=function(){
      if(this.status===200){
        resolve(this.response)
      }else{
        reject(new Error(this.statusText))
      }
    }
    xhr.send()
  })
}

// 測試
ajax('/api/foo.json')
.then(function(res){
  console.log(res)
})
.catch(function(error){
  console.log(error)
})

Promise常見誤區

就拿上面例子舉例,當我們從foo.json中獲取到資料後,需要再從bar.json中繼續獲取資料,有時會把程式碼寫成如下格式:

ajax('/api/foo.json')
.then(function(res){
  ajax('/api/bar.json')
  .then(function(res){
    console.log(res)
  }
})

這樣寫,還是形成了回撥地獄,正確的寫法可以是下面這樣:

ajax('/api/foo.json')
.then(function(res){
  // 在這裡處理資料
  // 。。。

  // 把第二個請求進行返回
  return ajax('/api/bar.json')
})
.then(function(res){
  // 這樣當我們還需要再進行更多的請求的時候可以接著像剛才這樣寫
  console.log(res)
  return ajax('/api/other.json')
})
.then(function(res){
  console.log(res)
})

Promise 並行執行

當多個請求之間並沒有因為關係時,可以讓其同時請求,即並行執行,使用Promise.all和Promise.race可以完成這一要求

// Promise.all需要等全域性請求都成功才能獲取到結果,結果是一個數組,對應傳入請求,失敗一個就全部失敗,進入catch
var promiseOfAll = Promise.all([
  ajax('/api/foo.json'),
  ajax('/api/bar.json'),
  ajax('/api/other.json')
])
promiseOfAll.then(function(values){
  // values是一個數組
  console.log(values)
})
.catch(function(error){
  console.log(error)
})

// Promise.race只等待第一個完成的請求,任何一個任務完成了,這個Promise就算完成了,全部失敗才會進入catch
var promiseOfRace = Promise.race([
  ajax('/api/foo.json'),
  ajax('/api/bar.json'),
  ajax('/api/other.json')
])
promiseOfRace.then(function(value){
  console.log(value)
})
.catch(function(error){
  console.log(error)
})

Promise的靜態方法

// 通過resolve方法把常量轉換成Promise物件
Promise.resolve('foo')
.then(function(value){
  console.log(value)
})
// 通過reject方法返回一個一定是失敗的Promise物件,其傳入的資料就是其失敗的理由
Promise.reject(new Error('rejected'))
.catch(function(error){
  console.log(error)
})

Promise 執行時序

如下例子:

console.log('global start')
setTimeout(()=>{
  console.log('setTimeout')
},0)
Promise.resolve()
.then(()=>{
  console.log('promise')
})
.then(()=>{
  console.log('Promise2')
})
.then(()=>{
  console.log('Promise3')
})
console.log('end')

輸出結果:
global start
end
Promise
Promise2
Promise3
setTimeout

結果分析:首先global start 和end是同步程式碼,會首先輸出,其次是setTimeout和Promise都可以看作是巨集任務,巨集任務的回撥可以是作為一個新的巨集任務進入到佇列中進行排隊,也可以是作為微任務,緊跟著當前巨集任務結束後立即執行,而Promise的then、catch、finally都是Promise的微任務,會在Promise之後立即執行,而setTimeout中的回撥則是一個新的巨集任務,會到佇列的未尾重新排隊,所以執行時序會靠後