精品欧美一区二区三区在线观看 _久久久久国色av免费观看性色_国产精品久久在线观看_亚洲第一综合网站_91精品又粗又猛又爽_小泽玛利亚一区二区免费_91亚洲精品国偷拍自产在线观看 _久久精品视频在线播放_美女精品久久久_欧美日韩国产成人在线

深入淺出JavaScript異步編程

開發(fā) 前端
單一的 Promise 鏈并不能凸顯 async/await 的優(yōu)勢(shì)。但是,如果處理流程比較復(fù)雜,那么整段代碼將充斥著 then,語(yǔ)義化不明顯,代碼不能很好地表示執(zhí)行流程,這時(shí)async/await的優(yōu)勢(shì)就能體現(xiàn)出來(lái)了。

瀏覽器中的 JavaScript 是典型的事件驅(qū)動(dòng)型程序,即它們會(huì)等待用戶觸發(fā)后才真正的執(zhí)行,而基于的JavaScript的服務(wù)器通常要等待客戶端通過(guò)網(wǎng)絡(luò)發(fā)送請(qǐng)求,然后才能執(zhí)行。這種異步編程在JavaScript是很常見(jiàn)的,下面就來(lái)介紹幾個(gè)異步編程的重要特性,它們可以使編寫異步代碼更容易。

本文將按照異步編程方式的出現(xiàn)時(shí)間來(lái)歸納整理:

圖片

一、什么是異步

下面先來(lái)看看同步和異步的概念:

  • 同步: 在執(zhí)行某段代碼時(shí),在沒(méi)有得到返回結(jié)果之前,其他代碼暫時(shí)是無(wú)法執(zhí)行的,但是一旦執(zhí)行完成拿到返回值,即可執(zhí)行其他代碼。也就是說(shuō),在此段代碼執(zhí)行完未返回結(jié)果之前,會(huì)阻塞之后的代碼執(zhí)行,這樣的情況稱為同步。
  • 異步: 當(dāng)某一代碼執(zhí)行異步過(guò)程調(diào)用發(fā)出后,這段代碼不會(huì)立刻得到返回結(jié)果。而是在異步調(diào)用發(fā)出之后,一般通過(guò)回調(diào)函數(shù)處理這個(gè)調(diào)用之后拿到結(jié)果。異步調(diào)用發(fā)出后,不會(huì)影響阻塞后面的代碼執(zhí)行,這樣的情況稱為異步。

下面來(lái)看一個(gè)例子:

// 同步
function syncAdd(a, b) {
  return a + b;
}

syncAdd(1, 2) // 立即得到結(jié)果:3

// 異步
function asyncAdd(a, b) {
  setTimeout(function() {
    console.log(a + b);
  }, 1000)
}

asyncAdd(1, 2) // 1s后打印結(jié)果:3

這里定義了同步函數(shù) syncAdd 和異步函數(shù) asyncAdd,調(diào)用 syncAdd(1, 2) 函數(shù)時(shí)會(huì)等待得到結(jié)果之后再執(zhí)行后面的代碼。而調(diào)用 asyncAdd(1, 2) 時(shí)則會(huì)在得到結(jié)果之前繼續(xù)執(zhí)行,直到 1 秒后得到結(jié)果并打印。

我們知道,JavaScript 是單線程的,如果代碼同步執(zhí)行,就可能會(huì)造成阻塞;而如果使用異步則不會(huì)阻塞,不需要等待異步代碼執(zhí)行的返回結(jié)果,可以繼續(xù)執(zhí)行該異步任務(wù)之后的代碼邏輯。因此,在 JavaScript 編程中,會(huì)大量使用異步。

那為什么單線程的JavaScript還能實(shí)現(xiàn)異步呢,其實(shí)也沒(méi)有什么魔法,只是把一些操作交給了其他線程處理,然后采用了事件循環(huán)的機(jī)制來(lái)處理返回結(jié)果。

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

在最基本的層面上,JavaScript的異步編程式通過(guò)回調(diào)實(shí)現(xiàn)的。回調(diào)的是函數(shù),可以傳給其他函數(shù),而其他函數(shù)會(huì)在滿足某個(gè)條件時(shí)調(diào)用這個(gè)函數(shù)。下面就來(lái)看看常見(jiàn)的不同形式的基于回調(diào)的異步編程。

1. 定時(shí)器

一種最簡(jiǎn)單的異步操作就是在一定時(shí)間之后運(yùn)行某些代碼。如下面代碼:

setTimeout(asyncAdd(1, 2), 8000)

setTimeout()方法的第一個(gè)參數(shù)是一個(gè)函數(shù),第二個(gè)參數(shù)是以毫秒為單位的時(shí)間間隔。asyncAdd()方法可能是一個(gè)回調(diào)函數(shù),而setTimeout()方法就是注冊(cè)回調(diào)函數(shù)的函數(shù)。它還代指在什么異步條件下調(diào)用回調(diào)函數(shù)。setTimeout()方法只會(huì)調(diào)用一次回調(diào)函數(shù)。

2. 事件監(jiān)聽

給目標(biāo) DOM 綁定一個(gè)監(jiān)聽函數(shù),用的最多的是 addEventListener:

document.getElementById('#myDiv').addEventListener('click', (e) => {
  console.log('我被點(diǎn)擊了')
}, false);

通過(guò)給 id 為 myDiv 的一個(gè)元素綁定了點(diǎn)擊事件的監(jiān)聽函數(shù),把任務(wù)的執(zhí)行時(shí)機(jī)推遲到了點(diǎn)擊這個(gè)動(dòng)作發(fā)生時(shí)。此時(shí),任務(wù)的執(zhí)行順序與代碼的編寫順序無(wú)關(guān),只與點(diǎn)擊事件有沒(méi)有被觸發(fā)有關(guān)。

這里使用addEventListener注冊(cè)了回調(diào)函數(shù),這個(gè)方法的第一個(gè)參數(shù)是一個(gè)字符串,指定要注冊(cè)的事件類型,如果用戶點(diǎn)擊了指定的元素,瀏覽器就會(huì)調(diào)用回調(diào)函數(shù),并給他傳入一個(gè)對(duì)象,其中包含著事件的詳細(xì)信息。

3. 網(wǎng)絡(luò)請(qǐng)求

JavaScript中另外一種常見(jiàn)的異步操作就是網(wǎng)絡(luò)請(qǐng)求:

const SERVER_URL = "/server";
let xhr = new XMLHttpRequest();
// 創(chuàng)建 Http 請(qǐng)求
xhr.open("GET", SERVER_URL, true);
// 設(shè)置狀態(tài)監(jiān)聽函數(shù)
xhr.onreadystatechange = function() {
  if (this.readyState !== 4) return;
  // 當(dāng)請(qǐng)求成功時(shí)
  if (this.status === 200) {
    handle(this.response);
  } else {
    console.error(this.statusText);
  }
};
// 設(shè)置請(qǐng)求失敗時(shí)的監(jiān)聽函數(shù)
xhr.onerror = function() {
  console.error(this.statusText);
};
// 發(fā)送 Http 請(qǐng)求
xhr.send(null);

這里使用XMLHttpRequest類及回調(diào)函數(shù)來(lái)發(fā)送HTTP請(qǐng)求并異步處理服務(wù)器返回的響應(yīng)。

4. Node中的回調(diào)與事件

Node.js服務(wù)端JavaScript環(huán)境底層就是異步的,定義了很多使用回調(diào)和事件的API。例如讀取文件默認(rèn)的API就是異步的,它會(huì)在讀取文件內(nèi)容之后調(diào)用一個(gè)回調(diào)函數(shù):

const fs = require('fs');
let options = {}

//  讀取配置文件,調(diào)用回調(diào)函數(shù)
fs.readFile('config.json', 'utf8', (err, data) => {
    if(err) {
      throw err;
    }else{
     Object.assign(options, JSON.parse(data))
    }
  startProgram(options)
});

fs.readFile()方法以接收兩個(gè)參數(shù)的回調(diào)作為最后一個(gè)參數(shù)。它會(huì)異步讀取指定文件,如果讀取成功就會(huì)將第二個(gè)參數(shù)傳遞給回調(diào)的第二個(gè)參數(shù),如果發(fā)生錯(cuò)誤,就會(huì)將錯(cuò)誤傳遞給回調(diào)的第一個(gè)參數(shù)。

三、Promise

1. Promise的概念

Promise是一種為簡(jiǎn)化異步編程而設(shè)計(jì)的核心語(yǔ)言特性,它是一個(gè)對(duì)象,表示異步操作的結(jié)果。在最簡(jiǎn)單的情況下,Promise就是一種處理回調(diào)的不同方式。不過(guò),使用Promise也有實(shí)際的用處,基于回調(diào)的異步編程會(huì)有一個(gè)很現(xiàn)實(shí)的問(wèn)題,那就是經(jīng)常出現(xiàn)回調(diào)多層嵌套的情況,會(huì)造成代碼難以理解。Promise可以讓這種嵌套回調(diào)以一種更線性的鏈?zhǔn)叫问奖磉_(dá)出來(lái),因此更容易閱讀和理解。

回調(diào)的另一個(gè)問(wèn)題就是難以處理錯(cuò)誤, 如果一個(gè)異步函數(shù)拋出異常,則該異常沒(méi)有辦法傳播到異步操作的發(fā)起者。異步編程的一個(gè)基本事實(shí)就是它破壞了異常處理。而Promise則標(biāo)準(zhǔn)化了異步錯(cuò)誤處理,通過(guò)Promise鏈提供一種讓錯(cuò)誤正確傳播的途經(jīng)。

實(shí)際上,Promise就是一個(gè)容器,里面保存著某個(gè)未來(lái)才會(huì)結(jié)束的事件(通常是異步操作)的結(jié)果。從語(yǔ)法上說(shuō),Promise 是一個(gè)對(duì)象,它可以獲取異步操作的消息。Promise 提供了統(tǒng)一的 API,各種異步操作都可以用同樣的方法進(jìn)行處理。

(1)Promise實(shí)例有三個(gè)狀態(tài):

  • pending 狀態(tài):表示進(jìn)行中。Promise 實(shí)例創(chuàng)建后的初始態(tài);
  • fulfilled 狀態(tài):表示成功完成。在執(zhí)行器中調(diào)用 resolve 后達(dá)成的狀態(tài);
  • rejected 狀態(tài):表示操作失敗。在執(zhí)行器中調(diào)用 reject 后達(dá)成的狀態(tài)。

(2)Promise實(shí)例有兩個(gè)過(guò)程:

  • pending -> fulfilled : Resolved(已完成);
  • pending -> rejected:Rejected(已拒絕)。

Promise的特點(diǎn):

  • 一旦狀態(tài)改變就不會(huì)再變,promise對(duì)象的狀態(tài)改變,只有兩種可能:從pending變?yōu)閒ulfilled,從pending變?yōu)閞ejected。當(dāng) Promise 實(shí)例被創(chuàng)建時(shí),內(nèi)部的代碼就會(huì)立即被執(zhí)行,而且無(wú)法從外部停止。比如無(wú)法取消超時(shí)或消耗性能的異步調(diào)用,容易導(dǎo)致資源的浪費(fèi);
  • 如果不設(shè)置回調(diào)函數(shù),Promise內(nèi)部拋出的錯(cuò)誤,不會(huì)反映到外部;
  • Promise 處理的問(wèn)題都是“一次性”的,因?yàn)橐粋€(gè) Promise 實(shí)例只能 resolve 或 reject 一次,所以面對(duì)某些需要持續(xù)響應(yīng)的場(chǎng)景時(shí)就會(huì)變得力不從心。比如上傳文件獲取進(jìn)度時(shí),默認(rèn)采用的就是事件監(jiān)聽的方式來(lái)實(shí)現(xiàn)。

下面來(lái)看一個(gè)例子:

const https = require('https');

function httpPromise(url){
  return new Promise((resolve,reject) => {
    https.get(url, (res) => {
      resolve(data);
    }).on("error", (err) => {
      reject(error);
    });
  })
}

httpPromise().then((data) => {
  console.log(data)
}).catch((error) => {
  console.log(error)
})

可以看到,Promise 會(huì)接收一個(gè)執(zhí)行器,在這個(gè)執(zhí)行器里,需要把目標(biāo)異步任務(wù)給放進(jìn)去。在 Promise 實(shí)例創(chuàng)建后,執(zhí)行器里的邏輯會(huì)立刻執(zhí)行,在執(zhí)行的過(guò)程中,根據(jù)異步返回的結(jié)果,決定如何使用 resolve 或 reject 來(lái)改變 Promise實(shí)例的狀態(tài)。

在這個(gè)例子里,當(dāng)用 resolve 切換到了成功態(tài)后,Promise 的邏輯就會(huì)走到 then 中傳入的方法里去;用 reject 切換到失敗態(tài)后,Promise 的邏輯就會(huì)走到 catch 傳入的方法中。

這樣的邏輯,本質(zhì)上與回調(diào)函數(shù)中的成功回調(diào)和失敗回調(diào)沒(méi)有差異。但這種寫法大大地提高了代碼的質(zhì)量。當(dāng)我們進(jìn)行大量的異步鏈?zhǔn)秸{(diào)用時(shí),回調(diào)地獄不復(fù)存在了。取而代之的是層級(jí)簡(jiǎn)單、賞心悅目的 Promise 調(diào)用鏈:

httpPromise(url1)
    .then(res => {
        console.log(res);
        return httpPromise(url2);
    })
    .then(res => {
        console.log(res);
        return httpPromise(url3);
    })
    .then(res => {
      console.log(res);
      return httpPromise(url4);
    })
    .then(res => console.log(res));

2. Promise的創(chuàng)建

Promise對(duì)象代表一個(gè)異步操作,有三種狀態(tài):pending(進(jìn)行中)、fulfilled(已成功)和rejected(已失敗)。

Promise構(gòu)造函數(shù)接受一個(gè)函數(shù)作為參數(shù),該函數(shù)的兩個(gè)參數(shù)分別是resolve和reject。

const promise = new Promise((resolve, reject) => {
  if (/* 異步操作成功 */){
    resolve(value);
  } else {
    reject(error);
  }
});

一般情況下,我們會(huì)用new Promise()來(lái)創(chuàng)建Promise對(duì)象。除此之外,還也可以使用promise.resolve和 promise.reject這兩個(gè)方法來(lái)創(chuàng)建:

(1)Promise.resolve

Promise.resolve(value)的返回值是一個(gè)promise對(duì)象,我們可以對(duì)返回值進(jìn)行.then調(diào)用,如下代碼:

Promise.resolve(11).then(function(value){
  console.log(value); // 打印出11
});

resolve(11)會(huì)讓promise對(duì)象進(jìn)入確定(resolve狀態(tài)),并將參數(shù)11傳遞給后面then中指定的onFulfilled 函數(shù);

(2)Promise.reject

Promise.reject 的返回值也是一個(gè)promise對(duì)象,如下代碼:

Promise.reject(new Error("我錯(cuò)了!"));

上面是以下代碼的簡(jiǎn)單形式:

new Promise((resolve, reject) => {
   reject(new Error("我錯(cuò)了!"));
});

下面來(lái)綜合看看resolve方法和reject方法:

function testPromise(ready) {
  return new Promise(resolve,reject) => {
    if(ready) {
      resolve("hello world");
    }else {
      reject("No thanks");
    }
  });
};

testPromise(true).then((msg) => {
  console.log(msg);
},(error) => {
  console.log(error);
});

上面的代碼給testPromise方法傳遞一個(gè)參數(shù),返回一個(gè)promise對(duì)象,如果為true,那么調(diào)用Promise對(duì)象中的resolve()方法,并且把其中的參數(shù)傳遞給后面的then第一個(gè)函數(shù)內(nèi),因此打印出 “hello world”, 如果為false,會(huì)調(diào)用promise對(duì)象中的reject()方法,則會(huì)進(jìn)入then的第二個(gè)函數(shù)內(nèi),會(huì)打印No thanks。

3. Promise的作用

在開發(fā)中可能會(huì)碰到這樣的需求:使用ajax發(fā)送A請(qǐng)求,成功后拿到數(shù)據(jù),需要把數(shù)據(jù)傳給B請(qǐng)求,那么需要這樣編寫代碼:

let fs = require('fs')
fs.readFile('./a.txt','utf8',function(err,data){
  fs.readFile(data,'utf8',function(err,data){
    fs.readFile(data,'utf8',function(err,data){
      console.log(data)
    })
  })
})

這段代碼之所以看上去很亂,歸結(jié)其原因有兩點(diǎn):

  • 第一是嵌套調(diào)用,下面的任務(wù)依賴上個(gè)任務(wù)的請(qǐng)求結(jié)果,并在上個(gè)任務(wù)的回調(diào)函數(shù)內(nèi)部執(zhí)行新的業(yè)務(wù)邏輯,這樣當(dāng)嵌套層次多了之后,代碼的可讀性就變得非常差了。
  • 第二是任務(wù)的不確定性,執(zhí)行每個(gè)任務(wù)都有兩種可能的結(jié)果(成功或者失敗),所以體現(xiàn)在代碼中就需要對(duì)每個(gè)任務(wù)的執(zhí)行結(jié)果做兩次判斷,這種對(duì)每個(gè)任務(wù)都要進(jìn)行一次額外的錯(cuò)誤處理的方式,明顯增加了代碼的混亂程度。

既然原因分析出來(lái)了,那么問(wèn)題的解決思路就很清晰了:

  • 消滅嵌套調(diào)用;
  • 合并多個(gè)任務(wù)的錯(cuò)誤處理。

這么說(shuō)可能有點(diǎn)抽象,不過(guò) Promise 解決了這兩個(gè)問(wèn)題。接下來(lái)就看看 Promise 是怎么消滅嵌套調(diào)用和合并多個(gè)任務(wù)的錯(cuò)誤處理的。

Promise出現(xiàn)之后,代碼可以這樣寫:

let fs = require('fs')
function read(url){
  return new Promise((resolve,reject)=>{
    fs.readFile(url,'utf8',function(error,data){
      error && reject(error)
      resolve(data)
    })
  })
}
read('./a.txt').then(data=>{
  return read(data) 
}).then(data=>{
  return read(data)  
}).then(data=>{
  console.log(data)
})

通過(guò)引入 Promise,上面這段代碼看起來(lái)就非常線性了,也非常符合人的直覺(jué)。Promise 利用了三大技術(shù)手段來(lái)解決回調(diào)地獄:回調(diào)函數(shù)延遲綁定、返回值穿透、錯(cuò)誤冒泡。

下面來(lái)看一段代碼:

let readFilePromise = (filename) => {
  fs.readFile(filename, (err, data) => {
    if(err) {
      reject(err);
    }else {
      resolve(data);
    }
  })
}
readFilePromise('1.json').then(data => {
  return readFilePromise('2.json')
});

可以看到,回調(diào)函數(shù)不是直接聲明的,而是通過(guò)后面的 then 方法傳入的,即延遲傳入,這就是回調(diào)函數(shù)延遲綁定。接下來(lái)針對(duì)上面的代碼做一下調(diào)整,如下:

let x = readFilePromise('1.json').then(data => {
  return readFilePromise('2.json')  //這是返回的Promise
});
x.then()

根據(jù) then 中回調(diào)函數(shù)的傳入值創(chuàng)建不同類型的 Promise,然后把返回的 Promise 穿透到外層,以供后續(xù)的調(diào)用。這里的 x 指的就是內(nèi)部返回的 Promise,然后在 x 后面可以依次完成鏈?zhǔn)秸{(diào)用。這便是返回值穿透的效果,這兩種技術(shù)一起作用便可以將深層的嵌套回調(diào)寫成下面的形式。

readFilePromise('1.json').then(data => {
    return readFilePromise('2.json');
}).then(data => {
    return readFilePromise('3.json');
}).then(data => {
    return readFilePromise('4.json');
});

這樣就顯得清爽許多,更重要的是,它更符合人的線性思維模式,開發(fā)體驗(yàn)更好,兩種技術(shù)結(jié)合產(chǎn)生了鏈?zhǔn)秸{(diào)用的效果。

這樣解決了多層嵌套的問(wèn)題,那另外一個(gè)問(wèn)題,即每次任務(wù)執(zhí)行結(jié)束后分別處理成功和失敗的情況怎么解決的呢?Promise 采用了錯(cuò)誤冒泡的方式。下面來(lái)看效果:

readFilePromise('1.json').then(data => {
    return readFilePromise('2.json');
}).then(data => {
    return readFilePromise('3.json');
}).then(data => {
    return readFilePromise('4.json');
}).catch(err => {
  // xxx
})

這樣前面產(chǎn)生的錯(cuò)誤會(huì)一直向后傳遞,被 catch 接收到,就不用頻繁地檢查錯(cuò)誤了。從上面的這些代碼中可以看到,Promise 解決效果也比較明顯:實(shí)現(xiàn)鏈?zhǔn)秸{(diào)用,解決多層嵌套問(wèn)題;實(shí)現(xiàn)錯(cuò)誤冒泡后一站式處理,解決每次任務(wù)中判斷錯(cuò)誤、增加代碼混亂度的問(wèn)題。

4. Promise的方法

Promise常用的方法:then()、catch()、all()、race()、finally()、allSettled()、any()。

(1)then()

當(dāng)Promise執(zhí)行的內(nèi)容符合成功條件時(shí),調(diào)用resolve函數(shù),失敗就調(diào)用reject函數(shù)。那Promise創(chuàng)建完了,該如何調(diào)用呢?這時(shí)就該then出場(chǎng)了:

promise.then(function(value) {
  // success
}, function(error) {
  // failure
});

then方法接受兩個(gè)回調(diào)函數(shù)作為參數(shù)。第一個(gè)回調(diào)函數(shù)是Promise對(duì)象的狀態(tài)變?yōu)閞esolved時(shí)調(diào)用,第二個(gè)回調(diào)函數(shù)是Promise對(duì)象的狀態(tài)變?yōu)閞ejected時(shí)調(diào)用。其中第二個(gè)參數(shù)可以省略。

then方法返回的是一個(gè)新的Promise實(shí)例。因此可以采用鏈?zhǔn)綄懛ǎ磘hen方法后面再調(diào)用另一個(gè)then方法。當(dāng)寫有順序的異步事件時(shí),需要串行時(shí),可以這樣寫:

let promise = new Promise((resolve,reject)=>{
    ajax('first').success(function(res){
        resolve(res);
    })
})
promise.then(res=>{
    return new Promise((resovle,reject)=>{
        ajax('second').success(function(res){
            resolve(res)
        })
    })
}).then(res=>{
    return new Promise((resovle,reject)=>{
        ajax('second').success(function(res){
            resolve(res)
        })
    })
}).then(res=>{
    
})

(2)catch()

Promise對(duì)象的catch方法相當(dāng)于then方法的第二個(gè)參數(shù),指向reject的回調(diào)函數(shù)。

不過(guò)catch方法還有一個(gè)作用,就是在執(zhí)行resolve回調(diào)函數(shù)時(shí),如果出現(xiàn)錯(cuò)誤,拋出異常,不會(huì)停止運(yùn)行,而是進(jìn)入catch方法中:

p.then((data) => {
     console.log('resolved',data);
},(err) => {
     console.log('rejected',err);
});

(3)all()

all方法可以完成并行任務(wù), 它接收一個(gè)數(shù)組,數(shù)組的每一項(xiàng)都是一個(gè)promise對(duì)象。當(dāng)數(shù)組中所有的promise的狀態(tài)都達(dá)到resolved時(shí),all方法的狀態(tài)就會(huì)變成resolved,如果有一個(gè)狀態(tài)變成了rejected,那么all方法的狀態(tài)就會(huì)變成rejected:

let promise1 = new Promise((resolve,reject)=>{
 setTimeout(()=>{
       resolve(1);
 },2000)
});
let promise2 = new Promise((resolve,reject)=>{
 setTimeout(()=>{
       resolve(2);
 },1000)
});
let promise3 = new Promise((resolve,reject)=>{
 setTimeout(()=>{
       resolve(3);
 },3000)
});

Promise.all([promise1,promise2,promise3]).then(res=>{
    console.log(res);  //結(jié)果為:[1,2,3] 
})

調(diào)用all方法時(shí)的結(jié)果成功的時(shí)候是回調(diào)函數(shù)的參數(shù)也是一個(gè)數(shù)組,這個(gè)數(shù)組按順序保存著每一個(gè)promise對(duì)象resolve執(zhí)行時(shí)的值。

(4)race()

race方法和all一樣,接受的參數(shù)是一個(gè)每項(xiàng)都是promise的數(shù)組,但與all不同的是,當(dāng)最先執(zhí)行完的事件執(zhí)行完之后,就直接返回該promise對(duì)象的值。

如果第一個(gè)promise對(duì)象狀態(tài)變成resolved,那自身的狀態(tài)變成了resolved;反之,第一個(gè)promise變成rejected,那自身狀態(tài)就會(huì)變成rejected。

let promise1 = new Promise((resolve,reject) => {
 setTimeout(() =>  {
       reject(1);
 },2000)
});
let promise2 = new Promise((resolve,reject) => {
 setTimeout(() => {
       resolve(2);
 },1000)
});
let promise3 = new Promise((resolve,reject) => {
 setTimeout(() => {
       resolve(3);
 },3000)
});
Promise.race([promise1,promise2,promise3]).then(res => {
 console.log(res); //結(jié)果:2
},rej => {
    console.log(rej)};
)

那么race方法有什么實(shí)際作用呢?當(dāng)需要執(zhí)行一個(gè)任務(wù),超過(guò)多長(zhǎng)時(shí)間就不做了,就可以用這個(gè)方法來(lái)解決:

Promise.race([promise1, timeOutPromise(5000)]).then(res => console.log(res))

(5)finally()

finally方法用于指定不管 Promise 對(duì)象最后狀態(tài)如何,都會(huì)執(zhí)行的操作。該方法是 ES2018 引入標(biāo)準(zhǔn)的。

promise.then(result => {···})
    .catch(error => {···})
       .finally(() => {···});

上面代碼中,不管promise最后的狀態(tài)如何,在執(zhí)行完then或catch指定的回調(diào)函數(shù)以后,都會(huì)執(zhí)行finally方法指定的回調(diào)函數(shù)。

下面來(lái)看例子,服務(wù)器使用 Promise 處理請(qǐng)求,然后使用finally方法關(guān)掉服務(wù)器。

server.listen(port)
  .then(function () {
    // ...
  })
  .finally(server.stop);

finally方法的回調(diào)函數(shù)不接受任何參數(shù),這意味著沒(méi)有辦法知道,前面的 Promise 狀態(tài)到底是fulfilled還是rejected。這表明,finally方法里面的操作,應(yīng)該是與狀態(tài)無(wú)關(guān)的,不依賴于 Promise 的執(zhí)行結(jié)果。

finally本質(zhì)上是then方法的特例:

promise
.finally(() => {
  // 語(yǔ)句
});

// 等同于
promise
.then(
  result => {
    // 語(yǔ)句
    return result;
  },
  error => {
    // 語(yǔ)句
    throw error;
  }
);

上面代碼中,如果不使用finally方法,同樣的語(yǔ)句需要為成功和失敗兩種情況各寫一次。有了finally方法,則只需要寫一次。

(6)allSettled()

Promise.allSettled 的語(yǔ)法及參數(shù)跟 Promise.all 類似,其參數(shù)接受一個(gè) Promise 的數(shù)組,返回一個(gè)新的 Promise。唯一的不同在于,執(zhí)行完之后不會(huì)失敗,也就是說(shuō)當(dāng) Promise.allSettled 全部處理完成后,我們可以拿到每個(gè) Promise 的狀態(tài),而不管其是否處理成功。

下面使用 allSettled 實(shí)現(xiàn)的一段代碼:

const resolved = Promise.resolve(2);
const rejected = Promise.reject(-1);
const allSettledPromise = Promise.allSettled([resolved, rejected]);
allSettledPromise.then(function (results) {
  console.log(results);
});
// 返回結(jié)果:
// [
//    { status: 'fulfilled', value: 2 },
//    { status: 'rejected', reason: -1 }
// ]

可以看到,Promise.allSettled 最后返回的是一個(gè)數(shù)組,記錄傳進(jìn)來(lái)的參數(shù)中每個(gè) Promise 的返回值,這就是和 all 方法不太一樣的地方。你也可以根據(jù) all 方法提供的業(yè)務(wù)場(chǎng)景的代碼進(jìn)行改造,其實(shí)也能知道多個(gè)請(qǐng)求發(fā)出去之后,Promise 最后返回的是每個(gè)參數(shù)的最終狀態(tài)。

(7)any()

any 方法返回一個(gè) Promise,只要參數(shù) Promise 實(shí)例有一個(gè)變成 fullfilled 狀態(tài),最后 any 返回的實(shí)例就會(huì)變成 fullfilled 狀態(tài);如果所有參數(shù) Promise 實(shí)例都變成 rejected 狀態(tài),包裝實(shí)例就會(huì)變成 rejected 狀態(tài)。

下面對(duì)上面 allSettled 這段代碼進(jìn)行改造,來(lái)看下改造完的代碼和執(zhí)行結(jié)果:

const resolved = Promise.resolve(2);
const rejected = Promise.reject(-1);
const allSettledPromise = Promise.any([resolved, rejected]);
allSettledPromise.then(function (results) {
  console.log(results);
});
// 返回結(jié)果:2

可以看出,只要其中一個(gè) Promise 變成 fullfilled 狀態(tài),那么 any 最后就返回這個(gè) Promise。由于上面 resolved 這個(gè) Promise 已經(jīng)是 resolve 的了,故最后返回結(jié)果為 2。

5. Promise的異常處理

錯(cuò)誤處理是所有編程范型都必須要考慮的問(wèn)題,在使用 JavaScript 進(jìn)行異步編程時(shí),也不例外。如果我們不做特殊處理,會(huì)怎樣呢?來(lái)看下面的代碼,先定義一個(gè)必定會(huì)失敗的方法

let fail = () => {
    setTimeout(() => {
 throw new Error("fail");
    }, 1000);
};

調(diào)用:

console.log(1);
try {
    fail();
} catch (e) {
    console.log("captured");
}
console.log(2);

可以看到打印出了 1 和 2,并在 1 秒后,獲得一個(gè)“Uncaught Error”的錯(cuò)誤打印,注意觀察這個(gè)錯(cuò)誤的堆棧:

Uncaught Error: fail
    at <anonymous>:3:9

可以看到,其中的 setTimeout (async) 這樣的字樣,表示著這是一個(gè)異步調(diào)用拋出的堆棧。但是,captured”這樣的字樣也并未打印,因?yàn)槟阜椒?fail() 本身的原始順序執(zhí)行并沒(méi)有失敗,這個(gè)異常的拋出是在回調(diào)行為里發(fā)生的。 從上面的例子可以看出,對(duì)于異步編程來(lái)說(shuō),我們需要使用一種更好的機(jī)制來(lái)捕獲并處理可能發(fā)生的異常。

Promise 除了支持 resolve 回調(diào)以外,還支持 reject 回調(diào),前者用于表示異步調(diào)用順利結(jié)束,而后者則表示有異常發(fā)生,中斷調(diào)用鏈并將異常拋出:

const exe = (flag) => () => new Promise((resolve, reject) => {
    console.log(flag);
    setTimeout(() => {
        flag ? resolve("yes") : reject("no");
    }, 1000);
});

上面的代碼中,flag 參數(shù)用來(lái)控制流程是順利執(zhí)行還是發(fā)生錯(cuò)誤。在錯(cuò)誤發(fā)生的時(shí)候,no 字符串會(huì)被傳遞給 reject 函數(shù),進(jìn)一步傳遞給調(diào)用鏈:

Promise.resolve()
       .then(exe(false))
       .then(exe(true));

上面的調(diào)用鏈,在執(zhí)行的時(shí)候,第二行就傳入了參數(shù) false,它就已經(jīng)失敗了,異常拋出了,因此第三行的 exe 實(shí)際沒(méi)有得到執(zhí)行,執(zhí)行結(jié)果如下:

false
Uncaught (in promise) no

這就說(shuō)明,通過(guò)這種方式,調(diào)用鏈被中斷了,下一個(gè)正常邏輯 exe(true) 沒(méi)有被執(zhí)行。 但是,有時(shí)候需要捕獲錯(cuò)誤,而繼續(xù)執(zhí)行后面的邏輯,該怎樣做?這種情況下就要在調(diào)用鏈中使用 catch 了:

Promise.resolve()
       .then(exe(false))
       .catch((info) => { console.log(info); })
       .then(exe(true));

這種方式下,異常信息被捕獲并打印,而調(diào)用鏈的下一步,也就是第四行的 exe(true) 可以繼續(xù)被執(zhí)行。將看到這樣的輸出:

false
no
true

6. Promise的實(shí)現(xiàn)

這一部分就來(lái)簡(jiǎn)單實(shí)現(xiàn)一下Promise及其常用的方法。

(1)Promise

const PENDING = "pending";
const RESOLVED = "resolved";
const REJECTED = "rejected";

function MyPromise(fn) {
  // 保存初始化狀態(tài)
  var self = this;

  // 初始化狀態(tài)
  this.state = PENDING;

  // 用于保存 resolve 或者 rejected 傳入的值
  this.value = null;

  // 用于保存 resolve 的回調(diào)函數(shù)
  this.resolvedCallbacks = [];

  // 用于保存 reject 的回調(diào)函數(shù)
  this.rejectedCallbacks = [];

  // 狀態(tài)轉(zhuǎn)變?yōu)?resolved 方法
  function resolve(value) {
    // 判斷傳入元素是否為 Promise 值,如果是,則狀態(tài)改變必須等待前一個(gè)狀態(tài)改變后再進(jìn)行改變
    if (value instanceof MyPromise) {
      return value.then(resolve, reject);
    }

    // 保證代碼的執(zhí)行順序?yàn)楸据喪录h(huán)的末尾
    setTimeout(() => {
      // 只有狀態(tài)為 pending 時(shí)才能轉(zhuǎn)變,
      if (self.state === PENDING) {
        // 修改狀態(tài)
        self.state = RESOLVED;

        // 設(shè)置傳入的值
        self.value = value;

        // 執(zhí)行回調(diào)函數(shù)
        self.resolvedCallbacks.forEach(callback => {
          callback(value);
        });
      }
    }, 0);
  }

  // 狀態(tài)轉(zhuǎn)變?yōu)?rejected 方法
  function reject(value) {
    // 保證代碼的執(zhí)行順序?yàn)楸据喪录h(huán)的末尾
    setTimeout(() => {
      // 只有狀態(tài)為 pending 時(shí)才能轉(zhuǎn)變
      if (self.state === PENDING) {
        // 修改狀態(tài)
        self.state = REJECTED;

        // 設(shè)置傳入的值
        self.value = value;

        // 執(zhí)行回調(diào)函數(shù)
        self.rejectedCallbacks.forEach(callback => {
          callback(value);
        });
      }
    }, 0);
  }

  // 將兩個(gè)方法傳入函數(shù)執(zhí)行
  try {
    fn(resolve, reject);
  } catch (e) {
    // 遇到錯(cuò)誤時(shí),捕獲錯(cuò)誤,執(zhí)行 reject 函數(shù)
    reject(e);
  }
}

MyPromise.prototype.then = function(onResolved, onRejected) {
  // 首先判斷兩個(gè)參數(shù)是否為函數(shù)類型,因?yàn)檫@兩個(gè)參數(shù)是可選參數(shù)
  onResolved =
    typeof onResolved === "function"
      ? onResolved
      : function(value) {
          return value;
        };

  onRejected =
    typeof onRejected === "function"
      ? onRejected
      : function(error) {
          throw error;
        };

  // 如果是等待狀態(tài),則將函數(shù)加入對(duì)應(yīng)列表中
  if (this.state === PENDING) {
    this.resolvedCallbacks.push(onResolved);
    this.rejectedCallbacks.push(onRejected);
  }

  // 如果狀態(tài)已經(jīng)凝固,則直接執(zhí)行對(duì)應(yīng)狀態(tài)的函數(shù)

  if (this.state === RESOLVED) {
    onResolved(this.value);
  }

  if (this.state === REJECTED) {
    onRejected(this.value);
  }
};

(2)Promise.then

then 方法返回一個(gè)新的 promise 實(shí)例,為了在 promise 狀態(tài)發(fā)生變化時(shí)(resolve / reject 被調(diào)用時(shí))再執(zhí)行 then 里的函數(shù),我們使用一個(gè) callbacks 數(shù)組先把傳給then的函數(shù)暫存起來(lái),等狀態(tài)改變時(shí)再調(diào)用。

那么,怎么保證后一個(gè) then 里的方法在前一個(gè) then(可能是異步)結(jié)束之后再執(zhí)行呢?

可以將傳給 then 的函數(shù)和新 promise 的 resolve 一起 push 到前一個(gè) promise 的 callbacks 數(shù)組中,達(dá)到承前啟后的效果:

  • 承前:當(dāng)前一個(gè) promise 完成后,調(diào)用其 resolve 變更狀態(tài),在這個(gè) resolve 里會(huì)依次調(diào)用 callbacks 里的回調(diào),這樣就執(zhí)行了 then 里的方法了
  • 啟后:上一步中,當(dāng) then 里的方法執(zhí)行完成后,返回一個(gè)結(jié)果,如果這個(gè)結(jié)果是個(gè)簡(jiǎn)單的值,就直接調(diào)用新 promise 的 resolve,讓其狀態(tài)變更,這又會(huì)依次調(diào)用新 promise 的 callbacks 數(shù)組里的方法,循環(huán)往復(fù)。。如果返回的結(jié)果是個(gè) promise,則需要等它完成之后再觸發(fā)新 promise 的 resolve,所以可以在其結(jié)果的 then 里調(diào)用新 promise 的 resolve
then(onFulfilled, onReject){
    // 保存前一個(gè)promise的this
    const self = this; 
    return new MyPromise((resolve, reject) => {
      // 封裝前一個(gè)promise成功時(shí)執(zhí)行的函數(shù)
      let fulfilled = () => {
        try{
          const result = onFulfilled(self.value); // 承前
          return result instanceof MyPromise? result.then(resolve, reject) : resolve(result); //啟后
        }catch(err){
          reject(err)
        }
      }
      // 封裝前一個(gè)promise失敗時(shí)執(zhí)行的函數(shù)
      let rejected = () => {
        try{
          const result = onReject(self.reason);
          return result instanceof MyPromise? result.then(resolve, reject) : reject(result);
        }catch(err){
          reject(err)
        }
      }
      switch(self.status){
        case PENDING: 
          self.onFulfilledCallbacks.push(fulfilled);
          self.onRejectedCallbacks.push(rejected);
          break;
        case FULFILLED:
          fulfilled();
          break;
        case REJECT:
          rejected();
          break;
      }
    })
   }

注意:

  • 連續(xù)多個(gè) then 里的回調(diào)方法是同步注冊(cè)的,但注冊(cè)到了不同的 callbacks 數(shù)組中,因?yàn)槊看?nbsp;then 都返回新的 promise 實(shí)例(參考上面的例子和圖)
  • 注冊(cè)完成后開始執(zhí)行構(gòu)造函數(shù)中的異步事件,異步完成之后依次調(diào)用 callbacks 數(shù)組中提前注冊(cè)的回調(diào)

(3)Promise.all

該方法的參數(shù)是 Promise 的實(shí)例數(shù)組, 然后注冊(cè)一個(gè) then 方法。 待數(shù)組中的 Promise 實(shí)例的狀態(tài)都轉(zhuǎn)為 fulfilled 之后則執(zhí)行 then 方法.,這里主要就是一個(gè)計(jì)數(shù)邏輯, 每當(dāng)一個(gè) Promise 的狀態(tài)變?yōu)?fulfilled 之后就保存該實(shí)例返回的數(shù)據(jù), 然后將計(jì)數(shù)減一, 當(dāng)計(jì)數(shù)器變?yōu)?nbsp;0 時(shí), 代表數(shù)組中所有 Promise 實(shí)例都執(zhí)行完畢.

Promise.all = function (arr) {
  let args = Array.prototype.slice.call(arr)
  return new Promise(function (resolve, reject) {
    if (args.length === 0) return resolve([])
    let remaining = args.length
    function res(i, val) {
      try {
        if (val && (typeof val === 'object' || typeof val === 'function')) {
          let then = val.then
          if (typeof then === 'function') {
            then.call(val, function (val) { // 這里如果傳入?yún)?shù)是 promise的話需要將結(jié)果傳入 args, 而不是 promise實(shí)例
              res(i, val) 
            }, reject)
            return
          }
        }
        args[i] = val
        if (--remaining === 0) {
          resolve(args)
        }
      } catch (ex) {
        reject(ex)
      }
    }
    for (let i = 0; i < args.length; i++) {
      res(i, args[i])
    }
  })
}

(4)Promise.race

該方法的參數(shù)是 Promise 實(shí)例數(shù)組, 然后其 then 注冊(cè)的回調(diào)方法是數(shù)組中的某一個(gè) Promise 的狀態(tài)變?yōu)?fulfilled 的時(shí)候就執(zhí)行. 因?yàn)?Promise 的狀態(tài)只能改變一次, 那么我們只需要把 Promise.race 中產(chǎn)生的 Promise 對(duì)象的 resolve 方法, 注入到數(shù)組中的每一個(gè) Promise 實(shí)例中的回調(diào)函數(shù)中即可:

oPromise.race = function (args) {
  return new oPromise((resolve, reject) => {
    for (let i = 0, len = args.length; i < len; i++) {
      args[i].then(resolve, reject)
    }
  })
}

四、Generator

1. Generator 概述

(1)Generator

Generator(生成器)是 ES6 中的關(guān)鍵詞,通俗來(lái)講 Generator 是一個(gè)帶星號(hào)的函數(shù)(它并不是真正的函數(shù)),可以配合 yield 關(guān)鍵字來(lái)暫停或者執(zhí)行函數(shù)。先來(lái)看一個(gè)例子:

function* gen() {
  console.log("enter");
  let a = yield 1;
  let b = yield (function () {return 2})();
  return 3;
}
var g = gen()           // 阻塞,不會(huì)執(zhí)行任何語(yǔ)句
console.log(typeof g)   // 返回 object 這里不是 "function"
console.log(g.next())
console.log(g.next())
console.log(g.next())
console.log(g.next())

輸出結(jié)果如下:

object
enter
{ value: 1, done: false }
{ value: 2, done: false }
{ value: 3, done: true }
{ value: undefined, done: true }

Generator 中配合使用 yield 關(guān)鍵詞可以控制函數(shù)執(zhí)行的順序,每當(dāng)執(zhí)行一次 next 方法,Generator 函數(shù)會(huì)執(zhí)行到下一個(gè)存在 yield 關(guān)鍵詞的位置。

總結(jié),Generator 的執(zhí)行的關(guān)鍵點(diǎn)如下:

  • 調(diào)用 gen() 后,程序會(huì)阻塞,不會(huì)執(zhí)行任何語(yǔ)句;
  • 調(diào)用 g.next() 后,程序繼續(xù)執(zhí)行,直到遇到 yield 關(guān)鍵詞時(shí)執(zhí)行暫停;
  • 一直執(zhí)行 next 方法,最后返回一個(gè)對(duì)象,其存在兩個(gè)屬性:value 和 done。

(2)yield

yield 同樣也是 ES6 的關(guān)鍵詞,配合 Generator 執(zhí)行以及暫停。yield 關(guān)鍵詞最后返回一個(gè)迭代器對(duì)象,該對(duì)象有 value 和 done 兩個(gè)屬性,其中 done 屬性代表返回值以及是否完成。yield 配合著 Generator,再同時(shí)使用 next 方法,可以主動(dòng)控制 Generator 執(zhí)行進(jìn)度。

下面來(lái)看看多個(gè) Generator 配合 yield 使用的情況:

function* gen1() {
    yield 1;
    yield* gen2();
    yield 4;
}
function* gen2() {
    yield 2;
    yield 3;
}
var g = gen1();
console.log(g.next())
console.log(g.next())
console.log(g.next())
console.log(g.next())

執(zhí)行結(jié)果如下:

{ value: 1, done: false }
{ value: 2, done: false }
{ value: 3, done: false }
{ value: 4, done: false }
{value: undefined, done: true}

可以看到,使用 yield 關(guān)鍵詞的話還可以配合著 Generator 函數(shù)嵌套使用,從而控制函數(shù)執(zhí)行進(jìn)度。這樣對(duì)于 Generator 的使用,以及最終函數(shù)的執(zhí)行進(jìn)度都可以很好地控制,從而形成符合你設(shè)想的執(zhí)行順序。即便 Generator 函數(shù)相互嵌套,也能通過(guò)調(diào)用 next 方法來(lái)按照進(jìn)度一步步執(zhí)行。

(3)生成器原理

其實(shí),在生成器內(nèi)部,如果遇到 yield 關(guān)鍵字,那么 V8 引擎將返回關(guān)鍵字后面的內(nèi)容給外部,并暫停該生成器函數(shù)的執(zhí)行。生成器暫停執(zhí)行后,外部的代碼便開始執(zhí)行,外部代碼如果想要恢復(fù)生成器的執(zhí)行,可以使用 result.next 方法。

那 V8 是怎么實(shí)現(xiàn)生成器函數(shù)的暫停執(zhí)行和恢復(fù)執(zhí)行的呢?

它用到的就是協(xié)程,協(xié)程是—種比線程更加輕量級(jí)的存在。我們可以把協(xié)程看成是跑在線程上的任務(wù),一個(gè)線程上可以存在多個(gè)協(xié)程,但是在線程上同時(shí)只能執(zhí)行一個(gè)協(xié)程。比如,當(dāng)前執(zhí)行的是 A 協(xié)程,要啟動(dòng) B 協(xié)程,那么 A 協(xié)程就需要將主線程的控制權(quán)交給 B 協(xié)程,這就體現(xiàn)在 A 協(xié)程暫停執(zhí)行,B 協(xié)程恢復(fù)執(zhí)行; 同樣,也可以從 B 協(xié)程中啟動(dòng) A 協(xié)程。通常,如果從 A 協(xié)程啟動(dòng) B 協(xié)程,我們就把 A 協(xié)程稱為 B 協(xié)程的父協(xié)程。

正如一個(gè)進(jìn)程可以擁有多個(gè)線程一樣,一個(gè)線程也可以擁有多個(gè)協(xié)程。每一時(shí)刻,該線程只能執(zhí)行其中某一個(gè)協(xié)程。最重要的是,協(xié)程不是被操作系統(tǒng)內(nèi)核所管理,而完全是由程序所控制(也就是在用戶態(tài)執(zhí)行)。這樣帶來(lái)的好處就是性能得到了很大的提升,不會(huì)像線程切換那樣消耗資源。

2. Generator 和 thunk 結(jié)合

下面先來(lái)了解一下什么是 thunk 函數(shù),以判斷數(shù)據(jù)類型為例:

let isString = (obj) => {
  return Object.prototype.toString.call(obj) === '[object String]';
};
let isFunction = (obj) => {
  return Object.prototype.toString.call(obj) === '[object Function]';
};
let isArray = (obj) => {
  return Object.prototype.toString.call(obj) === '[object Array]';
};
....

可以看到,這里出現(xiàn)了很多重復(fù)的判斷邏輯,平常在開發(fā)中類似的重復(fù)邏輯的場(chǎng)景也同樣會(huì)有很多。下面來(lái)進(jìn)行封裝:

let isType = (type) => {
  return (obj) => {
    return Object.prototype.toString.call(obj) === `[object ${type}]`;
  }
}

封裝之后就可以這樣使用,從而來(lái)減少重復(fù)的邏輯代碼:

let isString = isType('String');
let isArray = isType('Array');
isString("123");    // true
isArray([1,2,3]);   // true

相應(yīng)的 isString 和 isArray 是由 isType 方法生產(chǎn)出來(lái)的函數(shù),通過(guò)上面的方式來(lái)改造代碼,明顯簡(jiǎn)潔了不少。像 isType 這樣的函數(shù)稱為 thunk 函數(shù),它的基本思路都是接收一定的參數(shù),會(huì)生產(chǎn)出定制化的函數(shù),最后使用定制化的函數(shù)去完成想要實(shí)現(xiàn)的功能。

這樣的函數(shù)在 JS 的編程過(guò)程中會(huì)遇到很多,抽象度比較高的 JS 代碼往往都會(huì)采用這樣的方式。那 Generator 和 thunk 函數(shù)的結(jié)合是否能帶來(lái)一定的便捷性呢?

下面以文件操作的代碼為例,看一下 Generator 和 thunk 的結(jié)合能夠?qū)Ξ惒讲僮鳟a(chǎn)生的效果:

const readFileThunk = (filename) => {
  return (callback) => {
    fs.readFile(filename, callback);
  }
}
const gen = function* () {
  const data1 = yield readFileThunk('1.txt')
  console.log(data1.toString())
  const data2 = yield readFileThunk('2.txt')
  console.log(data2.toString)
}
let g = gen();
g.next().value((err, data1) => {
  g.next(data1).value((err, data2) => {
    g.next(data2);
  })
})

readFileThunk 就是一個(gè) thunk 函數(shù),上面的這種編程方式就讓 Generator 和異步操作關(guān)聯(lián)起來(lái)了。上面第三段代碼執(zhí)行起來(lái)嵌套的情況還算簡(jiǎn)單,如果任務(wù)多起來(lái),就會(huì)產(chǎn)生很多層的嵌套,可讀性不強(qiáng),因此有必要把執(zhí)行的代碼進(jìn)行封裝優(yōu)化:

function run(gen){
  const next = (err, data) => {
    let res = gen.next(data);
    if(res.done) return;
    res.value(next);
  }
  next();
}
run(g);

可以看到, run 函數(shù)和上面的執(zhí)行效果其實(shí)是一樣的。代碼雖然只有幾行,但其包含了遞歸的過(guò)程,解決了多層嵌套的問(wèn)題,并且完成了異步操作的一次性的執(zhí)行效果。這就是通過(guò) thunk 函數(shù)完成異步操作的情況。

3. Generator 和 Promise 結(jié)合

其實(shí) Promise 也可以和 Generator 配合來(lái)實(shí)現(xiàn)上面的效果。還是利用上面的輸出文件的例子,對(duì)代碼進(jìn)行改造,如下所示:

const readFilePromise = (filename) => {
  return new Promise((resolve, reject) => {
    fs.readFile(filename, (err, data) => {
      if(err) {
        reject(err);
      }else {
        resolve(data);
      }
    })
  }).then(res => res);
}
// 這塊和上面 thunk 的方式一樣
const gen = function* () {
  const data1 = yield readFilePromise('1.txt')
  console.log(data1.toString())
  const data2 = yield readFilePromise('2.txt')
  console.log(data2.toString)
}
// 這里和上面 thunk 的方式一樣
function run(gen){
  const next = (err, data) => {
    let res = gen.next(data);
    if(res.done) return;
    res.value(next);
  }
  next();
}
run(g);

可以看到,thunk 函數(shù)的方式和通過(guò) Promise 方式執(zhí)行效果本質(zhì)上是一樣的,只不過(guò)通過(guò) Promise 的方式也可以配合 Generator 函數(shù)實(shí)現(xiàn)同樣的異步操作。

4. co 函數(shù)庫(kù)

co 函數(shù)庫(kù)用于處理 Generator 函數(shù)的自動(dòng)執(zhí)行。核心原理其實(shí)就是通過(guò)和 thunk 函數(shù)以及 Promise 對(duì)象進(jìn)行配合,包裝成一個(gè)庫(kù)。它使用起來(lái)非常簡(jiǎn)單,比如還是用上面那段代碼,第三段代碼就可以省略了,直接引用 co 函數(shù),包裝起來(lái)就可以使用了,代碼如下:

const co = require('co');
let g = gen();
co(g).then(res =>{
  console.log(res);
})

這段代碼比較簡(jiǎn)單,幾行就完成了之前寫的遞歸的那些操作。那么為什么 co 函數(shù)庫(kù)可以自動(dòng)執(zhí)行 Generator 函數(shù),它的處理原理如下:

  1. 因?yàn)?Generator 函數(shù)就是一個(gè)異步操作的容器,它需要一種自動(dòng)執(zhí)行機(jī)制,co 函數(shù)接受 Generator 函數(shù)作為參數(shù),并最后返回一個(gè) Promise 對(duì)象。
  2. 在返回的 Promise 對(duì)象里面,co 先檢查參數(shù) gen 是否為 Generator 函數(shù)。如果是,就執(zhí)行該函數(shù);如果不是就返回,并將 Promise 對(duì)象的狀態(tài)改為 resolved。
  3. co 將 Generator 函數(shù)的內(nèi)部指針對(duì)象的 next 方法,包裝成 onFulfilled 函數(shù)。這主要是為了能夠捕捉拋出的錯(cuò)誤。
  4. 關(guān)鍵的是 next 函數(shù),它會(huì)反復(fù)調(diào)用自身。

五、Async/Await

1. async/await 的概念

ES7 新增了兩個(gè)關(guān)鍵字: async和await,代表異步JavaScript編程范式的遷移。它改進(jìn)了生成器的缺點(diǎn),提供了在不阻塞主線程的情況下使用同步代碼實(shí)現(xiàn)異步訪問(wèn)資源的能力。其實(shí) async/await 是 Generator 的語(yǔ)法糖,它能實(shí)現(xiàn)的效果都能用then鏈來(lái)實(shí)現(xiàn),它是為優(yōu)化then鏈而開發(fā)出來(lái)的。

從字面上來(lái)看,async是“異步”的簡(jiǎn)寫,await則為等待,所以 async 用來(lái)聲明異步函數(shù),這個(gè)關(guān)鍵字可以用在函數(shù)聲明、函數(shù)表達(dá)式、箭頭函數(shù)和方法上。因?yàn)楫惒胶瘮?shù)主要針對(duì)不會(huì)馬上完成的任務(wù),所以自然需要一種暫停和恢復(fù)執(zhí)行的能力,使用await關(guān)鍵字可以暫停異步代碼的執(zhí)行,等待Promise解決。async 關(guān)鍵字可以讓函數(shù)具有異步特征,但總體上代碼仍然是同步求值的。

它們的用法很簡(jiǎn)單,首先用 async 關(guān)鍵字聲明一個(gè)異步函數(shù):

async function httpRequest() {
}

然后就可以在這個(gè)函數(shù)內(nèi)部使用 await 關(guān)鍵字了:

async function httpRequest() {
  let res1 = await httpPromise(url1)
  console.log(res1)
}

這里,await關(guān)鍵字會(huì)接收一個(gè)期約并將其轉(zhuǎn)化為一個(gè)返回值或一個(gè)拋出的異常。通過(guò)情況下,我們不會(huì)使用await來(lái)接收一個(gè)保存期約的變量,更多的是把他放在一個(gè)會(huì)返回期約的函數(shù)調(diào)用面前,比如上述例子。這里的關(guān)鍵就是,await關(guān)鍵字并不會(huì)導(dǎo)致程序阻塞,代碼仍然是異步的,而await只是掩蓋了這個(gè)事實(shí),這就意味著任何使用await的代碼本身都是異步的。

下面來(lái)看看async函數(shù)返回了什么:

async function testAsy(){
   return 'hello world';
}
let result = testAsy(); 
console.log(result)

圖片

可以看到,async 函數(shù)返回的是 Promise 對(duì)象。如果異步函數(shù)使用return關(guān)鍵字返回了值(如果沒(méi)有return則會(huì)返回undefined),這個(gè)值則會(huì)被 Promise.resolve() 包裝成 Promise 對(duì)象。異步函數(shù)始終返回Promise對(duì)象。

2. await 到底在等啥?

那await到底在等待什么呢?

一般我們認(rèn)為 await 是在等待一個(gè) async 函數(shù)完成。不過(guò)按語(yǔ)法說(shuō)明,await 等待的是一個(gè)表達(dá)式,這個(gè)表達(dá)式的結(jié)果是 Promise 對(duì)象或其它值。

因?yàn)?async 函數(shù)返回一個(gè) Promise 對(duì)象,所以 await 可以用于等待一個(gè) async 函數(shù)的返回值——這也可以說(shuō)是 await 在等 async 函數(shù)。但要清楚,它等的實(shí)際是一個(gè)返回值。注意,await 不僅用于等 Promise 對(duì)象,它可以等任意表達(dá)式的結(jié)果。所以,await 后面實(shí)際是可以接普通函數(shù)調(diào)用或者直接量的。所以下面這個(gè)示例完全可以正確運(yùn)行:

function getSomething() {
    return "something";
}
async function testAsync() {
    return Promise.resolve("hello async");
}
async function test() {
    const v1 = await getSomething();
    const v2 = await testAsync();
    console.log(v1, v2);
}
test(); // something hello async

await 表達(dá)式的運(yùn)算結(jié)果取決于它等的是什么:

  • 如果它等到的不是一個(gè) Promise 對(duì)象,那 await 表達(dá)式的運(yùn)算結(jié)果就是它等到的內(nèi)容;
  • 如果它等到的是一個(gè) Promise 對(duì)象,await 就就會(huì)阻塞后面的代碼,等著 Promise 對(duì)象 resolve,然后將得到的值作為 await 表達(dá)式的運(yùn)算結(jié)果。

下面來(lái)看一個(gè)例子:

function testAsy(x){
   return new Promise(resolve=>{setTimeout(() => {
       resolve(x);
     }, 3000)
    }
   )
}
async function testAwt(){    
  let result =  await testAsy('hello world');
  console.log(result);    // 3秒鐘之后出現(xiàn)hello world
  console.log('cuger')   // 3秒鐘之后出現(xiàn)cug
}
testAwt();
console.log('cug')  //立即輸出cug

這就是 await 必須用在 async 函數(shù)中的原因。async 函數(shù)調(diào)用不會(huì)造成阻塞,它內(nèi)部所有的阻塞都被封裝在一個(gè) Promise 對(duì)象中異步執(zhí)行。await暫停當(dāng)前async的執(zhí)行,所以'cug''最先輸出,hello world'和 cuger 是3秒鐘后同時(shí)出現(xiàn)的。

3. async/await的優(yōu)勢(shì)

單一的 Promise 鏈并不能凸顯 async/await 的優(yōu)勢(shì)。但是,如果處理流程比較復(fù)雜,那么整段代碼將充斥著 then,語(yǔ)義化不明顯,代碼不能很好地表示執(zhí)行流程,這時(shí)async/await的優(yōu)勢(shì)就能體現(xiàn)出來(lái)了。

假設(shè)一個(gè)業(yè)務(wù),分多個(gè)步驟完成,每個(gè)步驟都是異步的,而且依賴于上一個(gè)步驟的結(jié)果。首先用 setTimeout 來(lái)模擬異步操作:

/**
 * 傳入?yún)?shù) n,表示這個(gè)函數(shù)執(zhí)行的時(shí)間(毫秒)
 * 執(zhí)行的結(jié)果是 n + 200,這個(gè)值將用于下一步驟
 */
function takeLongTime(n) {
    return new Promise(resolve => {
        setTimeout(() => resolve(n + 200), n);
    });
}
function step1(n) {
    console.log(`step1 with ${n}`);
    return takeLongTime(n);
}
function step2(n) {
    console.log(`step2 with ${n}`);
    return takeLongTime(n);
}
function step3(n) {
    console.log(`step3 with ${n}`);
    return takeLongTime(n);
}

現(xiàn)在用 Promise 方式來(lái)實(shí)現(xiàn)這三個(gè)步驟的處理:

function doIt() {
    console.time("doIt");
    const time1 = 300;
    step1(time1)
        .then(time2 => step2(time2))
        .then(time3 => step3(time3))
        .then(result => {
            console.log(`result is ${result}`);
            console.timeEnd("doIt");
        });
}
doIt();
// c:\var\test>node --harmony_async_await .
// step1 with 300
// step2 with 500
// step3 with 700
// result is 900
// doIt: 1507.251ms

輸出結(jié)果 result 是 step3() 的參數(shù) 700 + 200 = 900。doIt() 順序執(zhí)行了三個(gè)步驟,一共用了 300 + 500 + 700 = 1500 毫秒,和 console.time()/console.timeEnd() 計(jì)算的結(jié)果一致。

如果用 async/await 來(lái)實(shí)現(xiàn)呢,會(huì)是這樣:

async function doIt() {
    console.time("doIt");
    const time1 = 300;
    const time2 = await step1(time1);
    const time3 = await step2(time2);
    const result = await step3(time3);
    console.log(`result is ${result}`);
    console.timeEnd("doIt");
}
doIt();

結(jié)果和之前的 Promise 實(shí)現(xiàn)是一樣的,但是這個(gè)代碼看起來(lái)會(huì)清晰得多,幾乎和同步代碼一樣。

async/await對(duì)比Promise的優(yōu)勢(shì)就顯而易見(jiàn)了:

  • 代碼讀起來(lái)更加同步,Promise雖然擺脫了回調(diào)地獄,但是then的鏈?zhǔn)秸{(diào)?也會(huì)帶來(lái)額外的理解負(fù)擔(dān);
  • Promise傳遞中間值很麻煩,?async/await?乎是同步的寫法,?常優(yōu)雅;
  • 錯(cuò)誤處理友好,async/await可以?成熟的try/catch,Promise的錯(cuò)誤捕獲比較冗余;
  • 調(diào)試友好,Promise的調(diào)試很差,由于沒(méi)有代碼塊,不能在?個(gè)返回表達(dá)式的箭頭函數(shù)中設(shè)置斷點(diǎn),如果在?個(gè).then代碼塊中使?調(diào)試器的步進(jìn)(step-over)功能,調(diào)試器并不會(huì)進(jìn)?后續(xù)的.then代碼塊,因?yàn)檎{(diào)試器只能跟蹤同步代碼的每?步。

4. async/await 的異常處理

利用 async/await 的語(yǔ)法糖,可以像處理同步代碼的異常一樣,來(lái)處理異步代碼,這里還用上面的示例:

const exe = (flag) => () => new Promise((resolve, reject) => {
    console.log(flag);
    setTimeout(() => {
        flag ? resolve("yes") : reject("no");
    }, 1000);
});
const run = async () => {
 try {
  await exe(false)();
  await exe(true)();
 } catch (e) {
  console.log(e);
 }
}
run();

這里定義一個(gè)異步方法 run,由于 await 后面需要直接跟 Promise 對(duì)象,因此通過(guò)額外的一個(gè)方法調(diào)用符號(hào) () 把原有的 exe 方法內(nèi)部的 Thunk 包裝拆掉,即執(zhí)行 exe(false)() 或 exe(true)() 返回的就是 Promise 對(duì)象。在 try 塊之后,使用 catch 來(lái)捕捉。運(yùn)行代碼會(huì)得到這樣的輸出:

false
no

這個(gè) false 就是 exe 方法對(duì)入?yún)⒌妮敵觯@個(gè) no 就是 setTimeout 方法 reject 的回調(diào)返回,它通過(guò)異常捕獲并最終在 catch 塊中輸出。就像我們所認(rèn)識(shí)的同步代碼一樣,第四行的 exe(true) 并未得到執(zhí)行。

責(zé)任編輯:武曉燕 來(lái)源: 前端充電寶
相關(guān)推薦

2022-10-31 09:00:24

Promise數(shù)組參數(shù)

2022-09-26 09:01:15

語(yǔ)言數(shù)據(jù)JavaScript

2012-02-21 13:55:45

JavaScript

2010-07-16 09:11:40

JavaScript內(nèi)存泄漏

2011-05-30 14:41:09

Javascript閉

2021-03-16 08:54:35

AQSAbstractQueJava

2011-07-04 10:39:57

Web

2009-06-22 15:34:00

Javascript

2022-05-26 09:20:01

JavaScript原型原型鏈

2009-06-18 10:23:03

Javascript 基本框架

2021-07-20 15:20:02

FlatBuffers阿里云Java

2012-05-21 10:06:26

FrameworkCocoa

2017-07-02 18:04:53

塊加密算法AES算法

2019-01-07 15:29:07

HadoopYarn架構(gòu)調(diào)度器

2022-09-29 09:19:04

線程池并發(fā)線程

2025-02-06 09:47:33

2017-10-10 14:36:07

前端Javascriptapply、call、

2016-12-27 09:10:29

JavaScript原型鏈繼承

2019-11-11 14:51:19

Java數(shù)據(jù)結(jié)構(gòu)Properties

2009-11-30 16:46:29

學(xué)習(xí)Linux
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)

国产精品嫩模av在线| 免费看电影在线| 麻豆精品一区二区av白丝在线| 中文字幕欧美日韩精品| www.久久com| 精品众筹模特私拍视频| www精品美女久久久tv| 91精品久久久久久久久| 久久久久久久久久久久国产| 综合综合综合综合综合网| 欧美一区二区三区四区久久| 国产一区二区在线视频播放| 尤物视频在线免费观看| 成人av网站在线观看| 国产欧美日韩91| 日韩欧美一区二区一幕| 欧美丰满日韩| 精品亚洲aⅴ在线观看| 91香蕉视频免费看| 波多野结衣a v在线| 自拍视频在线看| 亚洲三级久久久| 久久综合久久久| 国产精品爽爽久久久久久| 在线视频日韩| 欧美大胆a视频| 国产馆在线观看| 欧美尿孔扩张虐视频| 欧美剧情片在线观看| 啊啊啊一区二区| 中文在线字幕免费观看| 中文字幕不卡的av| 免费h精品视频在线播放| 99久久精品日本一区二区免费| 久久久久久久波多野高潮日日| 色综合久久88| 777777国产7777777| 精品国产一区二区三区噜噜噜| 亚洲精品一区二区三区香蕉| 欧美在线a视频| 国精品产品一区| 色欧美片视频在线观看在线视频| www.国产在线视频| av小次郎在线| 综合色中文字幕| 午夜精品区一区二区三| 男操女在线观看| 91首页免费视频| 国产免费一区二区| 亚洲精品一区二区口爆| 国产成人av资源| 95av在线视频| a毛片在线免费观看| 国产在线精品免费| 国产在线日韩在线| 亚洲天堂网在线视频| 久久综合九色| 国产精品久久久999| 亚洲精品久久久久久久蜜桃| 久久综合影音| 国产精品国产三级国产专播精品人 | 亚洲福利精品在线| 91视频在线免费| 久久丝袜视频| 日韩精品小视频| 少妇特黄一区二区三区| 最新国产一区| 中文字幕国产精品久久| 亚洲天堂av中文字幕| 色狮一区二区三区四区视频| 深夜福利日韩在线看| 91麻豆精品久久毛片一级| 日韩av免费大片| 久久精品99国产精品酒店日本| 日韩av毛片在线观看| 天天揉久久久久亚洲精品| 久久成年人免费电影| 国产大片免费看| 精品二区视频| 日韩免费观看av| 中文字幕网址在线| 国产精品66部| 久久一区二区三区av| 国产免费av高清在线| 国产精品久久久久久久久久久免费看 | 在线免费视频a| 中文成人激情娱乐网| 日韩欧美成人午夜| 中文字幕av网址| 91免费精品| 久久久久久午夜| 日韩人妻精品中文字幕| 久久国产精品99久久久久久老狼| 亚洲一区二区自拍| 欧美日韩国产亚洲沙发| 亚洲视频在线一区二区| 久艹在线免费观看| 精品免费av一区二区三区| 欧美丰满高潮xxxx喷水动漫| 欧美图片自拍偷拍| 精品久久久久久久| 欧美第一黄网免费网站| 中文字幕在线天堂| 国产91富婆露脸刺激对白| 久中文字幕一区| a毛片在线观看| 色婷婷国产精品| 特种兵之深入敌后| 国产精品一区二区三区av麻| 久久久久999| 色一情一乱一伦| 国产精品1区2区3区在线观看| 欧美日韩一区二| 特级毛片在线| 欧美午夜免费电影| 亚洲一区二区三区综合| 91av精品| 国产精品美女免费看| 丰满少妇一级片| 亚洲天堂2014| 日本www.色| 亚洲精品合集| 欧美精品video| 国产精品国产精品国产专区| 国产亚洲一区二区在线观看| av免费看网址| 亚洲三区欧美一区国产二区| 中文字幕不卡av| 99久久久久久久久| 99视频一区二区| 一本大道东京热无码aⅴ| 日本国产亚洲| 国产亚洲视频在线观看| 一级片中文字幕| 国产91精品免费| 男女h黄动漫啪啪无遮挡软件| 欧美日韩女优| 亚洲欧美日韩在线一区| 伊人手机在线视频| a美女胸又www黄视频久久| 粉嫩av一区二区三区天美传媒 | chinese麻豆新拍video| 午夜久久tv| 亚洲a区在线视频| 思思99re6国产在线播放| 色菇凉天天综合网| 熟女少妇一区二区三区| 国产精品五区| 免费国产一区二区| 神马午夜在线视频| 日韩电影视频免费| 亚洲精品男人的天堂| 99精品视频在线播放观看| 男人天堂av片| 久久99国产精品久久99大师 | 欧美日韩成人影院| 亚洲偷熟乱区亚洲香蕉av| 神马久久久久久久| 久久精品欧美日韩精品| 性生交免费视频| 日韩黄色大片| 成人夜晚看av| 中文字幕中文字幕在线十八区 | 国产精品第二页| 国产成人天天5g影院在线观看| 91官网在线观看| 国产午夜精品福利视频| 韩国一区二区视频| 浴室偷拍美女洗澡456在线| 中文字幕一区日韩精品| 午夜精品久久久久久久99热浪潮| 亚洲人成色777777精品音频| 日韩欧美一区二区三区久久| 久久久久久亚洲中文字幕无码| 日韩专区一卡二卡| 中文字幕第一页亚洲| 日韩精品三级| 69久久夜色精品国产69| 大片免费播放在线视频| 一本久久精品一区二区| 中文字幕第24页| 国产精品综合在线视频| 91精品国产91久久久久麻豆 主演| 国产日韩三级| 国产91久久婷婷一区二区| 91在线导航| 日韩美女视频在线| 亚洲久久在线观看| 国产精品视频你懂的| 国产人妻精品午夜福利免费| 久久国产66| 日韩精品第1页| 外国成人在线视频| 成人免费网站在线| 国产精品高颜值在线观看| 亚洲午夜国产成人av电影男同| 国产又粗又长又大视频| 五月婷婷综合在线| 中文字幕乱码av| 91香蕉国产在线观看软件| 亚洲欧美偷拍另类| 一区二区日韩免费看| 一区精品视频| 色先锋久久影院av| 91美女片黄在线观| 中文字幕av一区二区三区佐山爱| 精品中文字幕在线| 不卡在线视频| 日韩av在线最新| 国产熟女一区二区丰满| 日韩欧美视频一区二区三区| 久久国产在线视频| 国产精品久久久久久久久动漫| 屁屁影院国产第一页| 久久99久国产精品黄毛片色诱| 欧美日韩在线一| 中文在线播放一区二区 | 中文字幕18页| 精品亚洲aⅴ乱码一区二区三区| 日日碰狠狠添天天爽超碰97| 欧美日韩日本国产亚洲在线| 日韩欧美亚洲在线| 国产suv精品一区二区四区视频| 国产精品欧美风情| 久久爱91午夜羞羞| 国内精品久久久久伊人av| 日本中文在线| 亚洲天堂影视av| 天堂中文在线8| 精品999久久久| 国产美女三级无套内谢| 欧洲一区在线观看| www.日本精品| 天天操天天综合网| 国产亚洲色婷婷久久99精品| 亚洲免费大片在线观看| 夫妇露脸对白88av| 欧美激情在线观看视频免费| 黑人巨大精品欧美| 91色视频在线| 97人妻精品一区二区三区免| 成人激情文学综合网| 国产高潮失禁喷水爽到抽搐| 国产一区二区三区视频在线播放| 911福利视频| 久久国产精品露脸对白| 一区二区三区四区毛片| 蜜桃精品视频在线| 黄色手机在线视频| 麻豆精品在线看| 日本中文字幕二区| 国产曰批免费观看久久久| 伊人色在线视频| 精品一区二区免费视频| 欧美性受xxxx黒人xyx性爽| 国产麻豆一精品一av一免费| 1314成人网| 成人美女视频在线观看18| 成人欧美精品一区二区| 成人美女在线视频| 久久国产精品无码一级毛片| 久久这里只精品最新地址| 久久久久久久毛片| 中文文精品字幕一区二区| 精品少妇一区二区三区密爱| 亚洲色图.com| 久久午夜无码鲁丝片| 天天操天天综合网| 亚洲av无码精品一区二区 | 亚洲成人一级片| 亚洲国内精品在线| 久久av少妇| 中文字幕日韩欧美在线视频| 久久99精品久久| 色综合天天狠天天透天天伊人| 成人国产电影在线观看| 日本不卡高字幕在线2019| 成人久久网站| av电影成人| 欧美美女在线观看| 一区二区日本伦理| 激情自拍一区| 爱情岛论坛成人| 国产成人免费xxxxxxxx| 人妻无码一区二区三区| 国产精品久久久久久妇女6080| 欧洲猛交xxxx乱大交3| 欧美日韩亚洲一区二区| 亚洲天堂中文网| 亚洲国产91色在线| 成年人免费在线视频| 欧美激情一区二区三区高清视频| 成人线上视频| **亚洲第一综合导航网站| 你懂的视频欧美| 日本精品福利视频| 天堂成人免费av电影一区| a级大片免费看| 久久精品人人爽人人爽| 国产亚洲小视频| 欧美日韩一区在线| 视频三区在线观看| 草民午夜欧美限制a级福利片| 神马久久午夜| 99一区二区| 欧美高清视频在线观看mv| 欧美一级在线看| 国产成人自拍高清视频在线免费播放 | va天堂va亚洲va影视| 欧美久久久久久一卡四| 亚洲91中文字幕无线码三区| 99精品视频在线看| 成人午夜碰碰视频| 成人做爰视频网站| 色哟哟一区二区三区| 蜜桃视频在线观看www| 中文字幕日韩专区| 在线天堂资源www在线污| av色综合网| 伊人青青综合网| 黄色永久免费网站| 久久蜜桃av一区二区天堂| 久草免费新视频| 欧美乱熟臀69xxxxxx| 国产在线黄色| 欧美中文在线字幕| 久久97精品| 黄页网站在线观看视频| 国产宾馆实践打屁股91| 午夜精品福利在线视频| 欧美无人高清视频在线观看| 日本大片在线观看| 2019中文字幕在线观看| 国产精品久久久久av蜜臀| 天天做天天爱天天高潮| 麻豆久久一区二区| 日本伦理一区二区三区| 91福利社在线观看| 国产人成在线观看| 国产精品久久久久免费a∨大胸| 伊人久久大香线蕉综合网站| 国产成人黄色片| 久久久噜噜噜久久中文字幕色伊伊 | yes4444视频在线观看| 国产精品电影观看| 日本在线电影一区二区三区| 天天操天天爱天天爽| 亚洲国产成人午夜在线一区| 色婷婷久久综合中文久久蜜桃av| 中文字幕日韩高清| 免费视频观看成人| 国产美女视频免费| 国产高清亚洲一区| 国产一级生活片| 精品亚洲一区二区| 少妇精品视频一区二区免费看| 亚洲第一在线综合在线| 久久成人羞羞网站| 久久精品一级片| 亚洲韩国青草视频| 免费污视频在线一区| 中文精品视频一区二区在线观看| 精品一区二区三区影院在线午夜| 国产极品国产极品| 亚洲激情成人网| 国模冰冰炮一区二区| 亚洲精品在线观看免费| 国产精品原创巨作av| 成年人免费看毛片| 伊人亚洲福利一区二区三区| 国产精品xnxxcom| 国产视频一视频二| 国产精品三级av| 国内精品偷拍视频| 欧洲成人免费视频| 日韩欧美在线中字| 日韩大尺度视频| 在线亚洲欧美专区二区| 动漫一区在线| 久久爱av电影| 精品一区精品二区高清| 91精品国产高潮对白| 亚洲人成电影网站色xx| 欧美伊人亚洲伊人色综合动图| 黄色影视在线观看| 久久成人av少妇免费| 国产女人被狂躁到高潮小说| 日韩黄在线观看| 日本黄色一区| 久久视频这里有精品| 国产视频在线观看一区二区三区| a网站在线观看| 欧美亚洲国产另类| 欧美日本一区二区视频在线观看| 手机免费看av片| 欧美日韩1区2区| 俺来俺也去www色在线观看| 亚洲精品高清视频| 成人午夜视频福利| 91亚洲国产成人精品一区|