欧美激情中文字幕在线_国产区精品视频_免费久久99精品国产自_日韩精品 欧美

ES6中的異步詳解

眾所周知JS是單線程的,這種設(shè)計(jì)讓JS避免了多線程的各種問題,但同時(shí)也讓JS同一時(shí)刻只能執(zhí)行一個(gè)任務(wù),若這個(gè)任務(wù)執(zhí)行時(shí)間很長(zhǎng)的話(如死循環(huán)),會(huì)導(dǎo)致JS直接卡死,在瀏覽器中的表現(xiàn)就是頁面無響應(yīng),用戶體驗(yàn)非常之差。

因此,在JS中有兩種任務(wù)執(zhí)行模式:同步(Synchronous)和異步(Asynchronous)。類似函數(shù)調(diào)用、流程控制語句、表達(dá)式計(jì)算等就是以同步方式運(yùn)行的,而異步主要由setTimeout/setInterval、事件實(shí)現(xiàn)。

傳統(tǒng)的異步實(shí)現(xiàn)

作為一個(gè)前端開發(fā)者,無論是瀏覽器端還是Node,相信大家都使用過事件吧,通過事件肯定就能想到回調(diào)函數(shù),它就是實(shí)現(xiàn)異步最常用、最傳統(tǒng)的方式。

不過要注意,不要以為回調(diào)函數(shù)就都是異步的,如ES5的數(shù)組方法Array.prototype.forEach((ele) => {})等等,它們也是同步執(zhí)行的。回調(diào)函數(shù)只是一種處理異步的方式,屬于函數(shù)式編程中高階函數(shù)的一種,并不只在處理異步問題中使用。

舉個(gè)栗子?:

// 最常見的ajax回調(diào)
this.ajax('/path/to/api', {
    params: params
}, (res) => {
    // do something...
})

你可能覺得這樣并沒有什么不妥,但是若有多個(gè)ajax或者異步操作需要依次完成呢?

this.ajax('/path/to/api', {
    params: params
}, (res) => {
    // do something...
    this.ajax('/path/to/api', {
      params: params
    }, (res) => {
        // do something...
        this.ajax('/path/to/api', {
          params: params
        }, (res) => {
          // do something...
        })
        ...
    })
})

回調(diào)地獄就出現(xiàn)了。。。?

為了解決這個(gè)問題,社區(qū)中提出了Promise方案,并且該方案在ES6中被標(biāo)準(zhǔn)化,如今已廣泛使用。

Promise

使用Promise的好處就是讓開發(fā)者遠(yuǎn)離了回調(diào)地獄的困擾,它具有如下特點(diǎn):

  1. 對(duì)象的狀態(tài)不受外界影響:
    • Promise對(duì)象代表一個(gè)異步操作,有三種狀態(tài):Pending(進(jìn)行中)、Resolved(已完成,又稱 Fulfilled)和Rejected(已失敗)。
    • 只有異步操作的結(jié)果,可以決定當(dāng)前是哪一種狀態(tài),任何其他操作都無法改變這個(gè)狀態(tài)。
  2. 一旦狀態(tài)改變,就不會(huì)再變,任何時(shí)候都可以得到這個(gè)結(jié)果。
    • Promise對(duì)象的狀態(tài)改變,只有兩種可能:從Pending變?yōu)镽esolved和從Pending變?yōu)镽ejected。
    • 只要這兩種情況發(fā)生,狀態(tài)就凝固了,不會(huì)再變了,會(huì)一直保持這個(gè)結(jié)果。
    • 如果改變已經(jīng)發(fā)生了,你再對(duì)Promise對(duì)象添加回調(diào)函數(shù),也會(huì)立即得到這個(gè)結(jié)果。
    • 這與事件(Event)完全不同,事件的特點(diǎn)是,如果你錯(cuò)過了它,再去監(jiān)聽,是得不到結(jié)果的。
  3. 一旦聲明Promise對(duì)象(new Promise或Promise.resolve等),就會(huì)立即執(zhí)行它的函數(shù)參數(shù),若不是函數(shù)參數(shù)則不會(huì)執(zhí)行
this.ajax('/path/to/api', {
    params: params
}).then((res) => {
    // do something...
    return this.ajax('/path/to/api', {
        params: params
    })
}).then((res) => {
    // do something...
    return this.ajax('/path/to/api', {
        params: params
    })
})
...

看起來就直觀多了,就像一個(gè)鏈條一樣將多個(gè)操作依次串了起來,再也不用擔(dān)心回調(diào)了~?

同時(shí)Promise還有許多其他API,如Promise.allPromise.racePromise.resolve/reject等等(可以參考阮老師的文章),在需要的時(shí)候配合使用都是極好的。

API無需多說,不過這里我總結(jié)了一下自己之前使用Promise踩到的坑以及我對(duì)Promise理解不夠透徹的地方,希望也能幫助大家更好地使用Promise:

1.then的返回結(jié)果:我之前天真的以為then要想鏈?zhǔn)秸{(diào)用,必須要手動(dòng)返回一個(gè)新的Promise才行

Promise.resolve('first promise')
.then((data) => {
    // return Promise.resolve('next promise')
    // 實(shí)際上兩種返回是一樣的
    return 'next promise'
})
.then((data) => {
    console.log(data)
})

總結(jié)如下:

  • 如果then方法中返回了一個(gè)值,那么返回一個(gè)“新的”resolved的Promise,并且resolve回調(diào)函數(shù)的參數(shù)值是這個(gè)值
  • 如果then方法中拋出了一個(gè)異常,那么返回一個(gè)“新的”rejected狀態(tài)的Promise
  • 如果then方法返回了一個(gè)未知狀態(tài)(pending)的Promise新實(shí)例,那么返回的新Promise就是未知狀態(tài)
  • 如果then方法沒有返回值時(shí),那么會(huì)返回一個(gè)“新的”resolved的Promise,但resolve回調(diào)函數(shù)沒有參數(shù)

2.一個(gè)Promise可設(shè)置多個(gè)then回調(diào),會(huì)按定義順序執(zhí)行,如下

const p = new Promise((res) => {
  res('hahaha')
})
p.then(console.log)
p.then(console.warn)

這種方式與鏈?zhǔn)秸{(diào)用不要搞混,鏈?zhǔn)秸{(diào)用實(shí)際上是then方法返回了新的Promise,而不是原有的,可以驗(yàn)證一下:

const p1 = Promise.resolve(123)
const p2 = p1.then(() => {
    console.log(p1 === p2)
    // false
})

3.thencatch返回的值不能是當(dāng)前promise本身,否則會(huì)造成死循環(huán)

const promise = Promise.resolve()
.then(() => {
    return promise
})

4.then或者catch的參數(shù)期望是函數(shù),傳入非函數(shù)則會(huì)發(fā)生值穿透

Promise.resolve(1)
  .then(2)
  .then(Promise.resolve(3))
  .then(console.log)
// 1

5.process.nextTickpromise.then都屬于microtask,而setImmediatesetTimeout屬于macrotask

process.nextTick(() => {
  console.log('nextTick')
})
Promise.resolve()
  .then(() => {
    console.log('then')
  })
setImmediate(() => {
  console.log('setImmediate')
})
console.log('end')
// end nextTick then setImmediate

有關(guān)microtaskmacrotask可以看這篇文章,講得很細(xì)致。

但Promise也存在弊端,那就是若步驟很多的話,需要寫一大串.then(),盡管步驟清晰,但是對(duì)于我們這些追求極致優(yōu)雅的前端開發(fā)者來說,代碼全都是Promise的API(thencatch),操作的語義太抽象,還是讓人不夠滿意呀~

Generator

Generator是ES6規(guī)范中對(duì)協(xié)程的實(shí)現(xiàn),但目前大多被用于異步模擬同步上了。

執(zhí)行它會(huì)返回一個(gè)遍歷器對(duì)象,而每次調(diào)用next方法則將函數(shù)執(zhí)行到下一個(gè)yield的位置,若沒有則執(zhí)行到return或末尾。

依舊是不再贅述API,對(duì)它還不了解的可以查閱阮老師的文章

通過Generator實(shí)現(xiàn)異步:

function* main() {
   const res = yield getData()
   console.log(res)
}
// 異步方法
function getData() {
   setTimeout(() => {
       it.next({
           name: 'yuanye',
           age: 22
       })
   }, 2000)
}
const it = main()
it.next()

先不管下面的next方法,單看main方法中,getData模擬的異步操作已經(jīng)看起來很像同步了。但是追求完美的我們肯定是無法忍受每次還要手動(dòng)調(diào)用next方法來繼續(xù)執(zhí)行流程的,為此TJ大神為社區(qū)貢獻(xiàn)了co模塊來自動(dòng)化執(zhí)行Generator,它的實(shí)現(xiàn)原理非常巧妙,源碼只有短短的200多行,感興趣可以去研究下。

const co = require('co')

co(function* () {
  const res1 = yield ['step-1']
  console.log(res1)
  // 若yield后面返回的是promise,則會(huì)等待它resolved后繼續(xù)執(zhí)行之后的流程
  const res2 = yield new Promise((res) => {
    setTimeout(() => {
      res('step-2')
    }, 2500)
  })
  console.log(res2)
  return 'end'
}).then((data) => {
  console.log('end: ' + data)
})

這樣就讓異步的流程完全以同步的方式展示出來啦?~

Async/Await

ES7標(biāo)準(zhǔn)中引入的async函數(shù),是對(duì)js異步解決方案的進(jìn)一步完善,它有如下特點(diǎn):

  1. 內(nèi)置執(zhí)行器:不用像generator那樣反復(fù)調(diào)用next方法,或者使用co模塊,調(diào)用即會(huì)自動(dòng)執(zhí)行,并返回結(jié)果
  2. 返回Promise:generator返回的是iterator對(duì)象,因此還不能直接用then來指定回調(diào)
  3. await更友好:相比co模塊約定的generator的yield后面只能跟promise或thunk函數(shù)或者對(duì)象及數(shù)組,await后面既可以是promise也可以是任意類型的值(Object、Number、Array,甚至Error等等,不過此時(shí)等同于同步操作)

進(jìn)一步說,async函數(shù)完全可以看作多個(gè)異步操作,包裝成的一個(gè)Promise對(duì)象,而await命令就是內(nèi)部then命令的語法糖

改寫后代碼如下:

async function testAsync() {
  const res1 = await new Promise((res) => {
    setTimeout(() => {
      res('step-1')
    }, 2000)
  })
  console.log(res1)
  const res2 = await Promise.resolve('step-2')
  console.log(res2)
  const res3 = await new Promise((res) => {
    setTimeout(() => {
      res('step-3')
    }, 2000)
  })
  console.log(res3)
  return [res1, res2, res3, 'end']
}

testAsync().then((data) => {
  console.log(data)
})

這樣不僅語義還是流程都非常清晰,即便是不熟悉業(yè)務(wù)的開發(fā)者也能一眼看出哪里是異步操作。

總結(jié)

本文匯總了當(dāng)前主流的JS異步解決方案,其實(shí)沒有哪一種方法最好或不好,都是在不同的場(chǎng)景下能發(fā)揮出不同的優(yōu)勢(shì)。而且目前都是Promise與其他兩個(gè)方案配合使用的,所以不存在你只學(xué)會(huì)async/await或者generator就可以玩轉(zhuǎn)異步。沒準(zhǔn)以后又會(huì)出現(xiàn)一個(gè)新的方案,將已有的這幾種方案顛覆呢 ~

說實(shí)話,學(xué)過后端的人玩JavaScript會(huì)陷入一種困境,如果讓程序員自己處理可能會(huì)更符合邏輯,比如引入線程之類的,不過優(yōu)化起來又是一個(gè)問題了。。。

來源:https://blog.markeyme.cn/2018/06/09/ES6%E5%BC%82%E6%AD%A5%E6%96%B9%E5%BC%8F%E5%85%A8%E9%9D%A2%E8%A7%A3%E6%9E%90/

上一篇 FancyBox3 中文文檔
下一篇 JavaScript中函數(shù)后面打星號(hào)方式聲明
目錄
applek

applek管理員

個(gè)人說明在個(gè)人中心里面設(shè)置

本月創(chuàng)作熱力圖

最新評(píng)論
小超
小超
7月9日
下個(gè)版本考慮將下載地址加密(防止采集)
Lvtu
Lvtu
7月4日
qiang厲害
評(píng)論于留言建議
chinacnd
chinacnd
6月26日
這個(gè)主題是怎么做到加載速度那么快的?
風(fēng)起云涌
風(fēng)起云涌
6月17日
前臺(tái)投稿現(xiàn)在有點(diǎn)太抽象了 希望可以弄成那種分區(qū)填寫的 標(biāo)題,內(nèi)容,圖片,目錄,標(biāo)簽,這樣的
評(píng)論于留言建議
主站蜘蛛池模板: 精品国产乱码久久久久| 国产h视频在线播放| 激情欧美一区二区三区中文字幕| 国产日韩亚洲欧美| 欧美亚洲激情在线| 99精品视频网站| 国模吧一区二区| 久久久久久久久91| 欧美中文在线观看国产| 国产精品视频最多的网站| 免费观看国产精品视频| 日韩人妻精品无码一区二区三区 | 久久精品久久精品国产大片| 色综合久久久久久中文网| 国产精品久久久久久亚洲影视| 久久99久久久久久久噜噜| 久久久久久69| 日本不卡在线观看| 欧美一区二区视频97| 欧美久久在线| 欧美精品v日韩精品v国产精品| 日本韩国在线不卡| 久久青草精品视频免费观看| 久久精品在线免费视频| 久久精品亚洲精品| 国产欧美精品日韩精品| 国产精品久久久久久久久久三级| 国产精品网红直播| 国产va免费精品高清在线| 国产精品免费久久久久影院| 国产欧美日韩亚洲| 国产精品视频26uuu| 国产欧美在线观看| 国产精品久久久久福利| 成人精品网站在线观看| 91免费看国产| 色综合久久天天综线观看| 欧美在线播放一区二区| 日本精品一区二区三区视频| 欧美国产激情视频| 国产日本欧美一区二区三区在线|