大前端學習--異步編程

異步編程

一、JS是單線程語言

JS執行環境找那個負責執行代碼的線程只有一個

執行任務的模式有兩種:同步模式、異步模式。


二、Promise

1. 基本使用
// Promise 基本演示

const promise = new Promise(function (resolve, reject) {
  // 這裏用於兌現承諾
  // resolve(100) // 承諾達成

  reject(new Error('promise rejected')) // 承諾失敗
})

promise.then(function (value) {
  console.log('resolved', value)
}, function (error) {
  console.log('rejected', error)
})

console.log('end') // 先打印出end,再打印Error
2. 通過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/users.json2').then(function (res) {
  console.log(res)
}, function (error) {
  console.log(error)
})
3. Promise通過鏈式調用避免回調嵌套
  • Promise對象的then方法會返回一個全新的Promise對象
  • 後面的then方法就是在爲上一個then返回的Promise註冊回調
  • 前面then方法中回調函數的返回值會作爲後面then方法回調的參數
  • 如果回調返回的是Promise,那後面then方法的回調會等待它的結束
function ajax(url) {
  return new Promise(function(resolve, reject) {
    // foo()
    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()
  })
}

var promise = ajax('/api/users.json')

// then 方法返回一個全新的promise對象
var promise2 = promise.then(function (res) {
  console.log(res)
}, function (error) {
  console.log(error)
})

console.log(promise2 === promise) // false

// 每一個then方法都是在爲上一個then方法添加狀態明確過後的回調
ajax('/api/users.json')
.then(function (value) {
  console.log(111)
  return ajax('/api/users.json')
}) // => Promise
.then(function (value) {
  console.log('yi', value)
  console.log(222)
  return 'foo'
}) // => Promise
.then(function (value) {
  console.log(333)
  console.log('jal', value)
}) // => Promise
.then(function (value) {
  console.log(444)
  console.log('ji', value)
}).catch(function onRejected(error) {
  console.log('onRejected', error)
})

其中catch也是then的別名

.catch(function onRejected(error) {
  console.log('onRejected', error)
})
// 就相當於
.then(undefined, function (value) {
  console.log(444)
  console.log('ji', value)
})

then中的第二個參數是reject函數,catch中的參數也是reject函數,但是作用不太相同,then中的reject不能捕獲到第一個參數中的resolve中的異常,但是catch由於鏈式作用,能捕獲到前面任意處的異常

ajax('/api/users.json')
.then(function (value) {
  console.log(111)
  return ajax('/api/users2.json') // 這個異常無法捕獲
}, function onRejected (e){
  console.log('reject', e)
}) 

推薦使用catch捕獲異常,可以捕獲整個promise鏈條上的異常:

ajax('/api/users.json')
.then(function (value) {
  console.log(111)
  return ajax('/api/users2.json')
}).catch(function onRejected (e){
  console.log('reject', e)
})

還可以全局捕獲異常:

瀏覽器中,在window對象上註冊事件:

window.addEventListener('unhandledrejection', event => {
  const {reason, promise} = event
  console.log(reason, promise)
  // reason => Promise 失敗原因,一般是一個錯誤對象
  // promise => 出現異常的Promise對象
  event.preventDefault()
}, false)

node中:

process.on('unhandledRejection', (reason, promise) => {
  const {reason, promise} = event
  console.log(reason, promise)
  // reason => Promise 失敗原因,一般是一個錯誤對象
  // promise => 出現異常的Promise對象
})

儘量在代碼中明確捕獲每一個可能的異常,而不是丟給全局處理。

5. Promise靜態方法
  • Promise.resolve() 快速的把一個值轉化爲Promise對象

    Promise.resolve('foo')
    .then(function (value) {
      console.log(value) // 'foo'
    })
    
    // Promise.resolve('foo') 等價於
    new Promise(function (resolve, reject){
      resolve('foo')
    })
    
    var promise = ajax('/api/users.json')
    var promise2 = Promise.resolve(promise)
    console.log(promise === promise2) // true
    
    // 帶有then方法的對象,就是實現了thenable接口, 可以被then的對象
    Promise.resolve({
      then: function (onFulfilled, onRejected) {
        onFulfilled('foo')
      }
    }).then(function (value){
      console.log(value) // foo
    })
    
  • Promise.reject(err) 傳入的對象爲失敗的原因

    Promise.reject('anything')
    .catch(function (err) {
      console.log(err) // anything
    })
    
6. Promise並行執行
  • Promise.all() 等待所有任務成功結束了,纔算結束

    var promise = Promise.all([
      ajax('/api/users.json'),
      ajax('/api/posts.json'),
    ])
    
    // 只有promise裏面的每一個任務都執行成功了才進入resolve
    // 其中任何一個失敗了,都會進入catch
    promise.then(function (values) {
      console.log(values) // 返回一個數組
    //   Array(2)
    // 0:
    // username: "yibo"
    // __proto__: Object
    // 1:
    // name: "jiailing"
    }).catch(function(err){
      console.log(err)
    })
    
    ajax('/api/urls.json')
    .then( value => {
      const urls = Object.values(value)
      const tasks = urls.map(url => ajax(url))
      return Promise.all(tasks)
    })
    .then(values => {
      console.log(values)
    })
    
  • Promise.race() 只會等待第一個結束的任務

    const request = ajax('/api/posts.json')
    const timeout = new Promise((resolve, reject) => {
      setTimeout(() => {
        reject(new Error('timeout'))
      }, 500);
    })
    Promise.race([
      request, timeout
    ])
    .then(value=>{
      console.log(value)
    })
    .catch(error=>{
      console.log(error)
    })
    // 實現ajax請求超時控制的一種方式
    
7. 微任務

即使Promise中沒有任何異步操作,它的回調函數仍然會進入到回調隊列中排隊。必須等待所有同步代碼執行完畢後,Promise中的代碼纔會被調用。

JS回調隊列中的任務稱之爲【宏任務】,而宏任務執行過程中可以臨時加上一些額外需求,可以選擇作爲一個新的宏任務進到隊列中排隊(如setTimeout),也可以作爲當前任務的【微任務】,直接在當前任務結束後立即執行。

Promise的回調會作爲微任務執行。微任務的目的是爲了提高整體的響應能力,目前絕大多數異步調用都是作爲宏任務執行,Promise 、MutationObserver、process.nextTick 是作爲微任務在本輪調用的末尾執行。

console.log('global start') // 第一個打印

Promise.resolve()
.then(()=>{
  console.log('promise')// 第3個打印
})
.then(()=>{
  console.log('promise 2')// 第4個打印
})
.then(()=>{
  console.log('promise 3')// 第5個打印
})
console.log('global end')// 第2個打印

setTimeout屬於宏任務

console.log('global start') // 第一個打印

setTimeout(() => {
  console.log('last') // 最後調用
}, 0);

Promise.resolve()
.then(()=>{
  console.log('promise')// 第3個打印
})
.then(()=>{
  console.log('promise 2')// 第4個打印
})
.then(()=>{
  console.log('promise 3')// 第5個打印
})
console.log('global end')// 第2個打印

三、Generator異步方案

1. Generator的基本使用

生成器函數會返回一個生成器對象,調用這個生成器對象的next方法,纔會讓函數體執行,一旦遇到了yield關鍵詞,函數的執行則會暫停下來,yield後面的值作爲next函數的結果返回,如果繼續調用函數的next函數,則會再上一次暫停的位置繼續執行,知道函數體執行完畢,next返回的對象的done就變成了true

function * fn () {
  console.log(111)
  yield 100
  console.log(222)
  yield 200
  console.log(333)
  yield  300
}

const generator = fn()
console.log(generator.next())
// 111
// { value: 100, done: false }
console.log(generator.next())
// 222
// { value: 200, done: false }
console.log(generator.next())
// 333
// { value: 300, done: false }
2. Generator實現異步

注意:generator.next(value)中,next傳入的參數會作爲上一次yield的返回值。

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()
  })
}
// 生成器函數
function * main () {
  const users = yield ajax('/api/users.json')
  console.log(users)
  
  const posts = yield ajax('/api/posts.json')
  console.log(posts)

  const urls = yield ajax('/api/urls.json')
  console.log(urls)
}
// 調用生成器函數得到一個生成器對象
const generator = main()

// 遞歸實現generator.next()的調用,直到done爲true終止
function dfs(value) {
  const result = generator.next(value)
  if(result.done) return
  result.value.then(data=>{
    console.log(data)
    dfs(data)
  })
}

dfs()
// 打印結果
// Generator實現異步.js:35 {username: "yibo"}
// Generator實現異步.js:19 {username: "yibo"}
// Generator實現異步.js:35 {posts: "jiailing"}
// Generator實現異步.js:22 {posts: "jiailing"}
// Generator實現異步.js:35 {posts: "/api/posts.json", users: "/api/users.json"}
// Generator實現異步.js:25 {posts: "/api/posts.json", users: "/api/users.json"}

封裝生成器函數執行器co

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()
  })
}
// 生成器函數
function * main () {
  try {
    const users = yield ajax('/api/users.json')
    console.log(users)
    
    const posts = yield ajax('/api/posts.json')
    console.log(posts)

    const urls = yield ajax('/api/urls.json')
    console.log(urls)
  } catch(e) {
    // 如果生成器函數中,發生了異常,會被生成器對象的throw方法捕獲
    console.log(e)
  }
}

// 封裝了一個生成器函數執行器
function co(main) {
  // 調用生成器函數得到一個生成器對象
  const generator = main()

  // 遞歸實現generator.next()的調用,直到done爲true終止
  function handleResult(result) {
    if(result.done) return
    result.value.then(data=>{
      console.log(data)
      handleResult(generator.next(data))
    }, error => {
      g.throw(error)
    })
  }

  handleResult(generator.next())
}

co(main)

// Generator實現異步.js:42 {username: "yibo"}
// Generator實現異步.js:20 {username: "yibo"}
// Generator實現異步.js:42 {posts: "jiailing"}
// Generator實現異步.js:23 {posts: "jiailing"}
// Generator實現異步.js:42 {posts: "/api/posts.json", users: "/api/users.json"}
// Generator實現異步.js:26 {posts: "/api/posts.json", users: "/api/users.json"}

四、Async/Await 語法糖

await關鍵詞只能出現在async函數中。

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()
  })
}

async function main () {
  try {
    const users = await ajax('/api/users.json')
    console.log(users)
    
    const posts = await ajax('/api/posts.json')
    console.log(posts)

    const urls = await ajax('/api/urls.json')
    console.log(urls)
  } catch(e) {
    console.log(e)
  }
}

main()

// async-await.js:20 {username: "yibo"}
// async-await.js:23 {posts: "jiailing"}
// async-await.js:26 {posts: "/api/posts.json", users: "/api/users.json"}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章