Javascript 非同步程式設計

Jack おう發表於2020-11-14

JS單執行緒的原因

如果多執行緒同時操作了dom,瀏覽器並不知道以誰為準。
優點:安全。
缺點:如果有耗時任務,會出現假死現象。
所以為了解決以上問題,JS有倆種模式
在這裡插入圖片描述

同步模式

程式碼依次執行。
函式的宣告不會入CallStack,呼叫的方法會進入CallStack,執行完成後彈出。可以想象成CallStack就是JS的任務執行表。
在這裡插入圖片描述
如果同步模式遇到耗時操作,可能會卡死,這個時候就需要進行非同步解決。

非同步模式

開啟非同步任務後,繼續執行同步程式碼,後續邏輯用回撥函式處理。
在這裡插入圖片描述
Timeout開啟定時器,進入WebApi後對Timeout說已經完畢,會彈出CallStack,繼續執行下一程式碼。
在這裡插入圖片描述

EventLoop負責呼叫棧和訊息佇列。

當CallStack清空後,EventLoop就會從訊息佇列中取出第一個回撥函式壓棧。
在這裡插入圖片描述
訊息佇列中發生了變化,事件迴圈就會監聽到,會就拿到佇列的第一個拿出來壓棧。
在這裡插入圖片描述
在這裡插入圖片描述
注意JS是單執行緒(執行程式碼的是單執行緒),但瀏覽器不是。JS某些API也不是單執行緒的,例如計時器,單獨開了執行緒。

回撥函式

非同步的本質就是回撥,例如 你根本不知道這個非同步任務何時完成,這個時候就需要一個方法來通知你。
在這裡插入圖片描述
在這裡插入圖片描述

Promise

Promise是個物件。
Promise只有三種狀態,待定 --》 成功或者失敗
在這裡插入圖片描述

// Promise 基本示例

const promise = new Promise(function (resolve, reject) {
  // 這裡用於“兌現”承諾

  // resolve(100) // 承諾達成

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

promise.then(function (value) {
  // 即便沒有非同步操作,then 方法中傳入的回撥仍然會被放入佇列,等待下一輪執行
  console.log('resolved', value)
}, function (error) {
  console.log('rejected', error)
})

console.log('end') 最先執行

注意:及時.then沒有回撥函式也會進入佇列排隊。

new的時候 promise就會被執行了。
在這裡插入圖片描述

Promise 方式的 AJAX

API

users.json
[
  {
    "name": "zce",
    "age": 24
  },
  {
    "name": "alan",
    "age": 25
  }
]
urls.json
{
  "users": "/api/users.json",
  "posts": "/api/posts.json"
}
posts.json
[
  {
    "title": "Hello world",
    "body": "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
  },
  {
    "title": "25 forever",
    "body": "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
  }
]

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)
}, function (error) {
  console.log(error)
})

Promise 常見誤區

// Promise 常見誤區

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

// 巢狀使用 Promise 是最常見的誤區
// ajax('/api/urls.json').then(function (urls) {
//   ajax(urls.users).then(function (users) {
//     ajax(urls.users).then(function (users) {
//       ajax(urls.users).then(function (users) {
//         ajax(urls.users).then(function (users) {

//         })
//       })
//     })
//   })
// })

Promise 鏈式呼叫

// Promise 鏈式呼叫

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

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

// var promise2 = promise.then(
//   function onFulfilled (value) {
//     console.log('onFulfilled', value)
//   },
//   function onRejected (error) {
//     console.log('onRejected', error)
//   }
// )

// console.log(promise2 === promise)  false  .then後返回全新的promise

ajax('/api/users.json')
  .then(function (value) {
    console.log(1111)
    return ajax('/api/urls.json')
  }) // => Promise
  .then(function (value) {
    console.log(2222)
    console.log(value)
    return ajax('/api/urls.json')
  }) // => Promise
  .then(function (value) {
    console.log(3333)
    return ajax('/api/urls.json')
  }) // => Promise
  .then(function (value) {
    console.log(4444)
    return 'foo'
  }) // => Promise
  .then(function (value) {
    console.log(5555)
    console.log(value)
  })

在這裡插入圖片描述

Promise 異常處理

// Promise 異常處理

function ajax (url) {
  return new Promise(function (resolve, reject) {
    // foo()
    // throw new Error()
    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/users11.json')
//   .then(function onFulfilled (value) {
//     console.log('onFulfilled', value)
//   }, function onRejected (error) {
//     console.log('onRejected', error)
//   })

// 使用 catch 註冊失敗回撥是更常見的

// ajax('/api/users11.json')
//   .then(function onFulfilled (value) {
//     console.log('onFulfilled', value)
//   })
//   .catch(function onRejected (error) {
//     console.log('onRejected', error)
//   })

// then(onRejected) 實際上就相當於 then(undefined, onRejected)

// ajax('/api/users11.json')
//   .then(function onFulfilled (value) {
//     console.log('onFulfilled', value)
//   })
//   .then(undefined, function onRejected (error) {
//     console.log('onRejected', error)
//   })

// 同時註冊的 onRejected 只是給當前 Promise 物件註冊的失敗回撥
// 它只能捕獲到當前 Promise 物件的異常

// ajax('/api/users.json')
//   .then(function onFulfilled (value) {
//     console.log('onFulfilled', value)
//     return ajax('/error-url')
//   }, function onRejected (error) {
//     console.log('onRejected', error)
//   })

// 因為 Promise 鏈條上的任何一個異常都會被一直向後傳遞,直至被捕獲
// 分開註冊的 onRejected 相當於給整個 Promise 鏈條註冊失敗回撥

ajax('/api/users.json')
  .then(function onFulfilled (value) {
    console.log('onFulfilled', value)
    return ajax('/error-url')
  }) // => Promise {}
  // .catch(function onRejected (error) {
  //   console.log('onRejected', error)
  // })

// 全域性捕獲 Promise 異常,類似於 window.onerror
window.addEventListener('unhandledrejection', event => {
  const { reason, promise } = event

  console.log(reason, promise)
  // reason => Promise 失敗原因,一般是一個錯誤物件
  // promise => 出現異常的 Promise 物件

  event.preventDefault()
}, false)

// Node.js 中使用以下方式
// process.on('unhandledRejection', (reason, promise) => {
//   console.log(reason, promise)
//   // reason => Promise 失敗原因,一般是一個錯誤物件
//   // promise => 出現異常的 Promise 物件
// })

catch和沒有catch 的區別
鏈式呼叫下
在這裡插入圖片描述

常用 Promise 靜態方法

// 常用 Promise 靜態方法

function ajax (url) {
  return new Promise(function (resolve, reject) {
    // foo()
    // throw new Error()
    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()
  })
}

// Promise.resolve('foo')
//   .then(function (value) {
//     console.log(value)
//   })

// new Promise(function (resolve, reject) {
//   resolve('foo')
// })

// 如果傳入的是一個 Promise 物件,Promise.resolve 方法原樣返回

// var promise = ajax('/api/users.json')
// var promise2 = Promise.resolve(promise)
// console.log(promise === promise2)  true

// 如果傳入的是帶有一個跟 Promise 一樣的 then 方法的物件,
// Promise.resolve 會將這個物件作為 Promise 執行

// Promise.resolve({
//   then: function (onFulfilled, onRejected) {
//     onFulfilled('foo')
//   }
// })
// .then(function (value) {
//   console.log(value)
// })

// Promise.reject 傳入任何值,都會作為這個 Promise 失敗的理由

// Promise.reject(new Error('rejected'))
//   .catch(function (error) {
//     console.log(error)
//   })

Promise.reject('anything')
  .catch(function (error) {
    console.log(error)
  })

Promise 並行執行

如果沒有互相依賴,就可以並行請求,節省時間。

// Promise 並行執行

function ajax (url) {
  return new Promise(function (resolve, reject) {
    // foo()
    // throw new Error()
    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.json')
// ajax('/api/posts.json')

// var promise = Promise.all([
//   ajax('/api/users.json'),
//   ajax('/api/posts.json')
// ])

// promise.then(function (values) {
//   console.log(values)
// }).catch(function (error) {
//   console.log(error)
// })

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

微任務 巨集任務

// 微任務

console.log('global start')

// setTimeout 的回撥是 巨集任務,進入回撥佇列排隊
setTimeout(() => {
  console.log('setTimeout')
}, 0)

// Promise 的回撥是 微任務,本輪呼叫末尾直接執行
Promise.resolve()
  .then(() => {
    console.log('promise')
  })
  .then(() => {
    console.log('promise 2')
  })
  .then(() => {
    console.log('promise 3')
  })

console.log('global end')

在這裡插入圖片描述

settimeout是巨集任務,巨集任務有了新的巨集任務後 會到最後尾繼續排隊,微任務會在同步任務結束後執行

在這裡插入圖片描述
但Promise和MutaitionObserver還有node的process.nexttick是微任務,在本輪呼叫的末尾就執行了。
在這裡插入圖片描述

generator

promise還是會有.then多層,可讀性還是沒有同步程式碼那麼高,這個時候我們還有更優的解決方案,generator和 Async / Await。generator可以作為了解。今後開發都會 Async / Await為主了。

// 生成器函式回顧

function * foo () {
  console.log('start')

  try {
    const res = yield 'foo'
    console.log(res)
  } catch (e) {
    console.log(e)
  }
}

const generator = foo()  不會立即執行

const result = generator.next()  
/// next後執行  執行到yield位置 把yield後面的值返回出去  foo函式就會暫停下拉
//next會返回一個 yield返回的值
console.log(result)


// generator.next('bar') 在yield暫定的位置繼續執行,這裡的bar會作為yield的返回值

generator.throw(new Error('Generator error'))

Generator 配合 Promise 的非同步方案

// Generator 配合 Promise 的非同步方案

function ajax (url) {
  return new Promise((resolve, reject) => {
    var xhr = new XMLHttpRequest()
    xhr.open('GET', url)
    xhr.responseType = 'json'
    xhr.onload = () => {
      if (xhr.status === 200) {
        resolve(xhr.response)
      } else {
        reject(new Error(xhr.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/urls11.json')
    console.log(urls)
  } catch (e) {
    console.log(e)
  }
}

function co (generator) {
  const g = generator()

  function handleResult (result) {
    if (result.done) return // 生成器函式結束
    result.value.then(data => {
      handleResult(g.next(data))
    }, error => {
      g.throw(error)
    })
  }

  handleResult(g.next())
}

co(main)  15年前比較流行

// const result = g.next()

// result.value.then(data => {
//   const result2 = g.next(data)

//   if (result2.done) return

//   result2.value.then(data => {
//     const result3 = g.next(data)

//     if (result3.done) return

//     result3.value.then(data => {
//       g.next(data)
//     })
//   })
// })

Async / Await 語法糖

// Async / Await 語法糖

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

function co (generator) {
  const g = generator()

  function handleResult (result) {
    if (result.done) return // 生成器函式結束
    result.value.then(data => {
      handleResult(g.next(data))
    }, error => {
      g.throw(error)
    })
  }

  handleResult(g.next())
}

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

// co(main)
const promise = main()

promise.then(() => {
  console.log('all completed')
})

相關文章