JS基礎(一)-暢遊promise世界

本文主要介紹了我的對promise的一些粗淺理解,若有不正確的地方,還請指正。css

一. Promise是什麼

從JS語法層面上講,Promise是一個函數,能夠經過new關鍵字進行調用而後生成一個對象,這個對象被成爲promise對象。promise對象有三種狀態,爲pedding、resolved、rejected,這是promise的重要機制。 從功能的角度來說,Promise是一個容器,它裏面包含着一個將來纔會獲得的(一般是異步)結果。 Promise幫助調用者拿回了調用回調函數的主動權,而不是把主動權交給第三方函數。前端

二. promise解決了什麼問題

2.1 回調的涉及的信任問題

1. 使用回調風格的異步處理

如下關於callback函數的執行有幾種信任問題。vue

function getData(url, callback) {
  let req = new XMLHttpRequest()
  req.open('GET', url, true)
  req.onload = function () {
    if (req.status === 200) {
      // 使用回調處理會存在幾種潛在的問題
      // 1. 過早或者過晚調用
      // 2. 乾脆不調用
      // 3. 調用了不少次
      // 4. 沒有給回調函數傳入指定的參數
      callback(req.responseText)
    } else {
      console.log(new Error(req.statusText))
    }
  }
  req.onerror = function () {
    console.log(new Error(req.statusText))
  }
  req.send()
}

function callback(text) {
  console.log(text)
}

let url = 'http://api.myjson.com/bins/16hvos'
getData(url, callback)
複製代碼

2. 使用promise的異步處理

function getData(url) {
  return new Promise(function (resolve, reject) {
    let req = new XMLHttpRequest()
    req.open('GET', url, true)
    req.onload = function () {
      if (req.status === 200) {
        resolve(req.responseText)
      } else {
        reject(new Error(req.statusText))
      }
    }
    req.onerror = function () {
      reject(new Error(req.statusText))
    }
    req.send()
  })
}

let url = 'http://api.myjson.com/bins/16hvos'
getData(url).then(function onFullfilled(value) {
  console.log(value)
}).catch(function onRejected(value) {
  console.log(value)
})
複製代碼

2.2 回調地獄

回調地獄會致使代碼晦澀難懂,不易維護,錯誤處理複雜。ios

1. 回調地獄(callback hell)示例代碼

// 先執行taskA 獲得符合預期結果; 執行taskB, 獲得預期結果,執行taskC, 獲得預期結果,接着處理。。。
function taskA(callback) {
  let result = 1
  setTimeout(() => {
    callback(result)
  }, 2000)
}

function taskB(callback) {
  let result = 2
  setTimeout(() => {
    callback(result)
  }, 2000)
}

function taskC(callback) {
  let result = 3
  setTimeout(() => {
    callback(result)
  }, 2000)
}

taskA(function (res) {
  if (res === 1) {
    taskB(function (res) {
      if (res === 2) {
        taskC(function (res) {
           if (res === 3) {
             console.log(res)
           }
        })
      }
    })
  }
})
複製代碼

能夠看出每執行一個函數都須要給其一個回調函數,並且代碼末尾有不少括號的嵌套,雖然可使用必定方法能夠改善這種嵌套的方法,可是依然代碼依然不夠易讀。好比採用以下方法抽離一部分代碼git

function a(res) {
  if (res === 1) {
    taskB(b)
  }
}

function b (res) {
  if (res === 2) {
    taskC(c)
  }
}

function c(res) {
  if (res === 3) {
    console.log(res)
  }
}

taskA(a) // 3
複製代碼

即便採用以上方式處理,閱讀此代碼仍然讓人頭大。es6

2. 使用promise處理回調地獄

// taskA執行,拿到預期結果,返回一個resolved的promise對象
function taskA() {
  let result = 1
  return new Promise(function (resolve, reject) {
    setTimeout(() => {
      if (result === 1) {
        resolve(result)
      } else {
        reject(new Error('fail'))
      }
    }, 2000)
  })
}

// 一個resolved狀態的promise對象能夠在其then方法中拿到異步處理的結果
// 在then方法中的回調函數中使用return會新生成一個resolved狀態的promise對象
taskA().then(function (result) {
  return result
}).then(function taskB() {
  let result = 2
  return new Promise(resolve => {
    setTimeout(() => {
      resolve(result)
    }, 2000)
  })
}).then(function taskC() {
  let result = 3
  return new Promise(resolve => {
    setTimeout(() => {
      resolve(result)
    }, 2000)
  })
}).then(function (result) {
  console.log(result)
})
複製代碼

三. promise的使用

3.1 建立一個promise對象

1. 構造函數形式

Promise函數接受一個函數做爲參數,這個函數有兩個參數,resolve和reject, 用來改變所生成的promise對象的狀態。這個函數參數會當即執行。github

let promise = new Promise(function (resolve, reject) {
  setTimeout(() => {
    resolve(42)
  }, 2000)
})
promise.then(function (value) {
  console.log(value) // 42
})

Object.getPrototypeOf(promise) === Promise.prototype // true
複製代碼

2. Promise.resolve

Promise.resolve() 直接返回一個已解析狀態的promise對象。vuex

var promise = Promise.resolve(42)
// 至關於
var promise = new Promise(function (resolve, reject) {
  resolve(42)
})
複製代碼

3. Promise.reject

Promise.reject 直接返回一個已拒絕狀態的promise對象。json

var promise = Promise.reject(new Error('ok'))
// 至關於
var promise = new Promise(function (resolve, reject) {
  reject(new Error('fail'))
})
複製代碼

3.2 promise#then 和 promise#catch

1. promise#then

(1) 每個promise對象都有then方法,當這個promise對象的狀態變爲resolved時,會執行then中註冊的回調函數。axios

let promise = Promise.resolve(42)
promise.then(function (value) {
  console.log(value) //42
})
複製代碼

(2) promise對象的then方法會返回一個新的promise對象,所以咱們能夠在新生成的promise對象中繼續使用then方法。

let promise = Promise.resolve(42)
promise.then(function (value) {
  console.log(value)
}).then(function (value) {
  console.log('ok')
})
複製代碼

根據then方法的特性能夠實現promise對象的鏈式調用。

let promise = Promise.resolve()

function taskA() {
  console.log('taskA')
}

function taskB() {
  console.log('taskB')
}

function onRejected(error) {
  console.log(error)
}

promise
  .then(taskA)
  .then(taskB)
  .catch(onRejected)
// taskA taskB 
複製代碼

以上例子中,實現了promise的鏈式調用。當taskA和taskB中沒有拋出錯誤時,程序會一直執行下去,其中catch是爲了捕獲taskA或taskB出現的異常。若是taskA或者taskB中出現了錯誤,那麼catch中的回調函數就會執行。以下:

let promise = Promise.resolve()

function taskA() {
  throw new Error('fail')
}

function taskB() {
  console.log('taskB')
}

function onRejected(error) {
  console.log(error)
}

promise
  .then(taskA)
  .then(taskB)
  .catch(onRejected)
// Error fail
複製代碼

若是taskA出現了一個錯誤,那麼taskB將不會執行,會執行調用catch中的回調函數。 3). 鏈式調用傳值 想要實現鏈式調用的傳值,只須要在then中的回調函數給出返回值,Promise就會自動將這個返回值解析爲一個promise對象。

let promise = Promise.resolve(10)

function taskA(value) {
  return value + 10
}

function taskB(value) {
  return value + 10 
}

function taskC(value) {
  console.log(value)
}

promise
  .then(taskA)
  .then(taskB)
  .then(taskC) // 30
複製代碼

2. promise#catch

當一個promise的狀態爲rejected時,那麼它的catch方法中的回調函數將會被執行。catch方法其實就是then方法中第二個回調函數。

let promise = Promise.reject('fail')
promise.catch(function (error) {
  console.log(error) // fail
})

// 至關於
promise.then(undefined, function (error) {
  console.log(error) // fail
})
複製代碼

catch也會返回一個promise對象。

3.3 Promise.all 和 Promise.race

1. Promise.all

Promise.all接受一個由promise對象組成的數組參數,當每一個promise的狀態都變成resoved或rejected時,Promise.all返回的promise對象的狀態纔會改變(resolved或者rejected)。

// getData函數返回一個promise, 而且函數體內的Promise函數會當即執行,即當即執行函數體內的異步任務
function getData(url) {
  return new Promise(function (resolve, reject) {
    var req = new XMLHttpRequest()
    req.open('GET', url, true)
    req.onload = function () {
      if (req.status === 200) {
        return req.responseText
      } else {
        return new Error(req.statusText)
      }
    }
    req.onerror = function () {
      reject(new Error(req.statusText))
    }
    req.send()
  })
}

// 錯誤寫法
// getData函數前加了async關鍵字,所以它也返回一個promise對象,可是函數體內的請求時異步的,
// getData會當即執行,而後再去執行異步任務,所以getData返回的promise永遠是resolved undefined
async function getData(url) {
  var req = new XMLHttpRequest()
  req.send()
  req.open('GET', url, true)
  req.onload = function () {
    if (req.status === 200) {
      console.log('ok')
      return req.responseText
    } else {
      return new Error(req.statusText)
    }
  }
  req.onerror = function () {
    reject(new Error(req.statusText))
  }
}

Promise.all([
  getData('http://azu.github.io/promises-book/json/comment.json'),
  getData('http://azu.github.io/promises-book/json/people.json')
]).then(res => {
  console.log(res)
})
複製代碼

2. Promise.race

Promise.race也接受一個由promise對象組成的數組參數,當其中的一個promise對象的狀態變爲resolved或是rejected時,Promise.race返回的promise對象纔會改變(resolved或者rejected)。

3.4 只執行異步操做的promise 和 async/await

1. 一個當即變爲resolved狀態的promise對象,promise.then中註冊的回調函數仍是會被以異步方式調用。
function asyncPromise () {
  return new Promise(resolve => {
    resolve(3)
  })
}

function test() {
  asyncPromise().then(function (value) {
    console.log(value)
  })
  console.log(1)
}

test()
console.log(2)
// 輸出順序爲 1 2 3
複製代碼

2. 一個function前加了async關鍵字,那麼這個函數會返回一個promise對象

能夠把async當作是一個返回值爲promise對象的函數的語法糖。

async function test() {
  return 1
}
test() // Promise {<resolved>: 1} google瀏覽器的打印結果

// 至關於
function test() {
  return Promise.resolve(1)
}
test() // Promise {<resolved>: 1} google瀏覽器的打印結果
複製代碼

3. await 關鍵字後面若是是一個promise對象,那麼它會把這個promise對象解析後的結果取出來,而且其後面的不論是同步代碼仍是異步代碼都會等到這個promise對象解析出來後(由pedding變爲resolved或rejected),纔會執行後面的代碼。

await 讓異步代碼變爲了同步(同步執行)。

function asyncFunction () {
  return new Promise(resolve => {
    resolve(2)
  })
}

async function test() {
  let value = await asyncFunction()
  console.log(value)
  console.log(3)
}

test()
console.log(1)
// 打印結果爲 1 2 3 
複製代碼

4. 項目實踐

採用兩種方式模擬一個通過vuex獲取數據的過程。 (1) 直接返回promise對象的方式

// src/People.vue
created () {
  this.$store.dispatch('GetPeople', { page: 1, size: 20 }).then(response => {
    console.log(response) // [{ id: 1, name: 'Joe'}]
  })
}

// src/store/people/actions.js
GetPeople({ commit }, payload) {
  return new Promise(function (resolve, reject) {
    axios.get('http://www.example.com/people', { payload })
      .then(function (response) {
         resolve(response) // response = [{ id: 1, name: 'Joe'}]
      }).catch(function (error) {
         reject(error)
      })
  })
}
複製代碼

(2) async / await

// src/People.vue
async created() {
  await this.$store.dispatch('GetPeople', { page: 1, size: 20 })
  // [{ id: 1, name: 'Joe'}]
}

// src/store/people/actions.js
async GetPeople({ commit }, payload) {
  const response = await axios.get('http://www.example.com/people', { payload })
  return response
}
複製代碼

參考:

you don't know js

promise 迷你書

<關於咱們>

咱們是來自帝都的一枚前端程序猿 + 一枚前端程序媛。

這裏發佈的文章是咱們對學習內容的總結,預計會每週至少會更新一篇。

目前咱們學習計劃是: 小程序實戰 => vue 進階用法 => vue 原理 => css 基礎 => es6 => js 深刻

另外,工做中用到的一些技術和完成的功能,咱們也會及時總結更新在這裏

如文章有錯誤或表述不清晰,歡迎各位留言反饋~~

相關文章
相關標籤/搜索