" />

欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

JavaScript異步編程操作實現(xiàn)介紹

 更新時間:2022年09月09日 08:31:31   作者:ExMaterial  
異步(Asynchronous, async)是與同步(Synchronous, sync)相對的概念。在我們學習的傳統(tǒng)單線程編程中,程序的運行是同步的,同步不意味著所有步驟同時運行,而是指步驟在一個控制流序列中按順序執(zhí)行,而異步的概念則是不保證同步的概念

異步編程

目前主流的JavaScript執(zhí)行環(huán)境都是以單線程執(zhí)行JavaScript的。

JavaScript早期只是一門負責在瀏覽器端執(zhí)行的腳本語言,主要用來操作DOM,如果其添加的同時又刪除了DOM,瀏覽器就不知道該如何是好,所以其就被設計成為單線程模型。而隨著JavaScript能做的事情越來越多,如果一直維持同步編程的話,就會導致瀏覽器卡在某個耗時操作無法進行下一步,造成瀏覽器假死的現(xiàn)象,影響用戶體驗。因此,異步編程應運而生。

同步模式與異步模式

同步模式(Synchronous)

同步模式是指代碼是同步執(zhí)行的,下一步的代碼執(zhí)行必須要等到上一步的代碼完成之后才能執(zhí)行,執(zhí)行順序就為代碼的編寫順序。

console.log('global begin')
function bar() {
    console.log('bar task')
}
function foo() {
    console.log('foo task')
    bar()
}
foo()
console.log('global end')

異步模式(Asynchronous)

不會去等待這個任務的執(zhí)行完成才去執(zhí)行下一個任務,開啟過后就立即開始下一個任務,后續(xù)邏輯一般會通過回調(diào)函數(shù)來進行定義。

console.log('global begin')
setTimeout(function timer1 () {
  console.log('timer1 invoke')
}, 1800)
setTimeout(function timer2 () {
  console.log('timer2 invoke')

  setTimeout(function inner () {
    console.log('inner invoke')
  }, 1000)
}, 1000)
console.log('global end')

回調(diào)函數(shù)

回調(diào)函數(shù)——所有異步編程方案的根基

其實回調(diào)函數(shù)就是封裝你想要對某些數(shù)據(jù)進行的操作,等到你想要進行的操作結(jié)束后,再調(diào)用這個函數(shù)。

一講起回調(diào)函數(shù),面試中一般都會被問到,什么是回調(diào)地獄?如何解決回調(diào)地獄。以下面代碼為例:

// 回調(diào)地獄,只是示例,不能運行
$.get('/url1', function (data1) {
  $.get('/url2', data1, function (data2) {
    $.get('/url3', data2, function (data3) {
      $.get('/url4', data3, function (data4) {
        $.get('/url5', data4, function (data5) {
          $.get('/url6', data5, function (data6) {
            $.get('/url7', data6, function (data7) {
              // 略微夸張了一點點
            })
          })
        })
      })
    })
  })
})

一大串的回調(diào)不僅難以閱讀,當代碼出現(xiàn)錯誤時,找出代碼錯誤更是一種折磨。幸運的是,JavaScript是在不斷發(fā)展的,在ES2015(ES6)中,出現(xiàn)了一種解決方法,媽媽再也不用擔心我寫代碼碰到回調(diào)地獄了。

Promise

Promise——一種更優(yōu)的異步編程統(tǒng)一方案

你可以把Promise理解成“承諾”或者“期約”(js高程作者的翻譯,想了解的話,可以去看看紅寶書第四版),你已經(jīng)聲明了這個東西,它在未來的時間一定會執(zhí)行,你可以相信它。

首先,你要了解Promise是有三種狀態(tài),即pending(等待)、onFulfilled(完成)、onRejected(失?。瓿苫蚴顟B(tài)一旦確定,就是無法更改的。

Promise基本用法

const promise = new Promise(function (resolve, reject) {
  // 注意,要得到reject的結(jié)果時要先把resolved的代碼注釋掉,原因上面已經(jīng)解釋了
  resolve(100)    // 兌現(xiàn)承諾
  reject(new Error('promise rejected'))   // 承諾失敗
})
promise.then(function(value) {
  console.log('resolved', value)
}, function(error) {
  console.log('rejected', error)
})
console.log('end')

每個new Promise都接受兩個參數(shù),第一個為兌現(xiàn)承諾的函數(shù),會將函數(shù)中的值傳遞給promise實例,reject等同。而返回的promise又自帶一個then方法,也接受兩個參數(shù),一個代表成功的回調(diào),一個代表失敗的回調(diào)。

Promise使用案例

在當前文件夾下新建一個文件夾,其中隨便放兩個json文件,用來模擬ajax請求。

// Promise 方式的 AJAX
function ajax(url) {
  return new Promise(function(resolve, reject) {
    let 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/posts.json').then(function (res) {
  console.log(res)
}, function(err) {
  console.log(err)
})

注意,上述代碼應該在瀏覽器端運行。

Promise常見誤區(qū)

有人學了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) {
        })
      })
    })
  })
})

說實話,這樣寫還不如不寫。正經(jīng)寫法是鏈式調(diào)用,學過jQuery的同學應該不會陌生吧。

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異常處理

通過.catch方法來捕獲異常。

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

其實catch方法和then方法實現(xiàn)差不太多,不過是then方法第一個參數(shù)傳入undefine,一個語法糖而已。還有一個有意思的現(xiàn)象是,當中間的then出現(xiàn)錯誤時,會直接穿透到最后的catch方法,有興趣了解怎么實現(xiàn)的可以去看看源碼。相信你一定會有所收獲。

Promise靜態(tài)方法

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

該方法會直接將傳入的內(nèi)容當作一個onFulfilled對象返回;其也可以傳入一個Promise對象,直接返回Promise.resolve方法;傳入一個帶有then方法的函數(shù)也同理。

  • Promise.reject

該方法和上述一樣調(diào)用,不過后面是接一個catch。

  • Promise.all

該方法接受一個數(shù)組,會等待數(shù)組內(nèi)的方法全部調(diào)用完后再返回一個數(shù)組對象。

  • Promise.race

該方法會返回最先完成的promise。

宏任務與微任務

// 微任務
console.log('global start')
// setTimeout 的回調(diào)是 宏任務,進入回調(diào)隊列排隊
setTimeout(() => {
  console.log('setTimeout')
}, 0)
// Promise 的回調(diào)是 微任務,本輪調(diào)用末尾直接執(zhí)行
Promise.resolve()
  .then(() => {
    console.log('promise')
  })
  .then(() => {
    console.log('promise 2')
  })
  .then(() => {
    console.log('promise 3')
  })
console.log('global end')

每次調(diào)用宏任務之前,都得確保微任務隊列清空,所以也就能理解上面為什么會按照那樣的順序進行輸出。

常見的宏任務有

  • setTimeout
  • setInterval
  • setImmediate
  • I/O
  • UI rendering

常見的微任務有

  • promise
  • nextTick
  • mutationObserver

Generator 異步方案

ES6也推出了Generator異步解決方案。首先來看下生成器函數(shù)如何使用。

生成器函數(shù)回顧

function *foo() {
  console.log('satrt')
  try {
    const res = yield 'foo'
    console.log(res)
  } catch (e) {
    console.log(e)
  }
}
const generator = foo()
const result = generator.next()
console.log(result)
generator.throw(new Error('Generato error'))

生成器函數(shù)比普通函數(shù)多了個 * ,其放左放右都無所謂,我個人傾向于放右邊。

其內(nèi)部有一個next方法,返回一個對象,大概就是這個樣子

{ value: 'foo', done: false }

value為yield返回的值,當然,如果你調(diào)用yield時傳入了值,返回的值就是你傳入的值。當執(zhí)行完畢時,done就變成了true。

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 // 生成器函數(shù)結(jié)束
    result.value.then(data => {
      handleResult(g.next(data))
    }, error => {
      g.throw(error)
    })
  }
  handleResult(g.next())
}
co(main)

你只要理解了這個,即要通過你調(diào)用next方法才會進行到下一步,否則代碼就會停在yield那里。不過,你這樣每次享受優(yōu)美代碼時都還是需要自己編寫一個co函數(shù),未免有點太過麻煩。不過不用擔心,async就要出場了。

async與await

async、await——可能是異步的終極解決方案

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

你只需要在函數(shù)定義前加一個async,并在你想要等待的完成操作后的函數(shù)前加一個await,就可以實現(xiàn)同步的書寫代碼而異步調(diào)用,怎么樣?這是不是更加優(yōu)雅方便了呢?鑒于目前ECMAScript的發(fā)展趨勢,保不準哪一天不需要async,直接用await就能實現(xiàn)異步編程了。

到此這篇關(guān)于JavaScript異步編程操作實現(xiàn)介紹的文章就介紹到這了,更多相關(guān)JS異步編程內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

最新評論