ES6(十一)—— Promise(更優(yōu)的異步編程解決方案)

目錄

  • 說到Promise就不得不說道說道這 —— 回調(diào)地獄
  • Promise —— 解決回調(diào)地獄
    • Promise語法規(guī)范
    • Promise的狀態(tài)
    • Promise基本用法
    • Promise初體驗(yàn)
    • Promise的本質(zhì)
    • Promise鏈?zhǔn)秸{(diào)用
      • 常見誤區(qū)
      • 鏈?zhǔn)秸{(diào)用的理解
  • Promise.prototype.then()
  • Promise異常處理
    • then中回調(diào)的onRejected方法
    • Promise.prototype.catch()(推薦)
      • .catch形式和前面then里面的第二個(gè)參數(shù)的形式菩颖,兩者異常捕獲的區(qū)別:
    • 全局對象上的unhandledrejection事件
  • Promise靜態(tài)方法
    • 類型轉(zhuǎn)換 —— Promise.resolve()
      • 使用場景
    • Promise.reject()
    • 數(shù)據(jù)聚合 —— Promise.all()
    • 競爭 —— Promise.race()
  • Promise執(zhí)行時(shí)序 —— 宏任務(wù) vs 微任務(wù)
  • 深度剖析:手寫一個(gè)Promise源碼
  • ES6-ES10學(xué)習(xí)版圖

說到Promise就不得不說道說道這 —— 回調(diào)地獄

a => b => c => d

回調(diào)層數(shù)越深酵紫,那么回調(diào)的維護(hù)成本越高

//異步加載函數(shù)
function loadScript (src, callback) {
    let script = document.createElement('script')
    script.src = src
    script.onload = () => {
        callback()
    }
    document.head.append(script)
}

function test () {
    console.log('test')
}
loadScript('./1.js', test)

// 1
// test

如果有三個(gè)這樣的方式回調(diào)

function loadScript (src, callback) {
    let script = document.createElement('script')
    script.src = src
    script.onload = () => {
        callback(src)
    }
    document.head.append(script)
}

function test (name) {
    console.log(name)
}
loadScript('./1.js', function (script) {
    console.log(script)
    loadScript('./2.js', function (script) {
        console.log(script)
        loadScript('./3.js', function (script) {
            console.log(script)
            //...
        })
    })
})

// 1
// ./1.js
// 2
// ./2.js
// 3
// ./3.js

Promise —— 解決回調(diào)地獄

雖然回調(diào)函數(shù)是所有異步編程方案的根基应闯。但是如果我們直接使用傳統(tǒng)回調(diào)方式去完成復(fù)雜的異步流程躁劣,就會無法避免大量的回調(diào)函數(shù)嵌套。導(dǎo)致回調(diào)地獄的問題躬存。

為了避免這個(gè)問題犁柜。CommonJS社區(qū)提出了Promise的規(guī)范,ES6中稱為語言規(guī)范疯兼。

Promise是一個(gè)對象,用來表述一個(gè)異步任務(wù)執(zhí)行之后是成功還是失敗贫途。

Promise語法規(guī)范

new Promise( function(resolve, reject) {…} );

  • new Promise(fn) 返回一個(gè)Promise 對象
  • fn中指定異步等處理
    • 處理結(jié)果正常的話吧彪,調(diào)用resolve(處理結(jié)果值)
    • 處理結(jié)果錯(cuò)誤的話,調(diào)用reject(Error對象)

Promise的狀態(tài)

Promise 內(nèi)部是有狀態(tài)的 (pending丢早、fulfilled姨裸、rejected)Promise 對象根據(jù)狀態(tài)來確定執(zhí)行哪個(gè)方法怨酝。Promise 在實(shí)例化的時(shí)候狀態(tài)是默認(rèn) pending 的傀缩,

  • 當(dāng)異步操作是完成的,狀態(tài)會被修改為 fulfilled
  • 如果異步操作遇到異常农猬,狀態(tài)會被修改為 rejected赡艰。
    無論修改為哪種狀態(tài),之后都是不可改變的斤葱。
image

Promise基本用法

返回resolve

const promise = new Promise((resolve, reject) => {
  resolve(100)
})

promise.then((value) => {
  console.log('resolved', value) // resolve 100
},(error) => {
  console.log('rejected', error)
})

返回reject

const promise = new Promise((resolve, reject) => {
  reject(new Error('promise rejected'))
})

promise.then((value) => {
  console.log('resolved', value)
},(error) => {
  console.log('rejected', error)
  // rejected Error: promise rejected
  //  at E:\professer\lagou\Promise\promise-example.js:4:10
  //  at new Promise (<anonymous>)
})

即便promise中沒有任何的異步操作慷垮,then方法的回調(diào)函數(shù)仍然會進(jìn)入到事件隊(duì)列中排隊(duì)揖闸。

Promise初體驗(yàn)

使用Promise去封裝一個(gè)ajax的案例

function ajax (url) {
  return new Promise((resolve, rejects) => {
    // 創(chuàng)建一個(gè)XMLHttpRequest對象去發(fā)送一個(gè)請求
    const xhr = new XMLHttpRequest()
    // 先設(shè)置一下xhr對象的請求方式是GET,請求的地址就是參數(shù)傳遞的url
    xhr.open('GET', url)
    // 設(shè)置返回的類型是json料身,是HTML5的新特性
    // 我們在請求之后拿到的是json對象汤纸,而不是字符串
    xhr.responseType = 'json'
    // html5中提供的新事件,請求完成之后(readyState為4)才會執(zhí)行
    xhr.onload = () => {
      if(this.status === 200) {
        // 請求成功將請求結(jié)果返回
        resolve(this.response)
      } else {
        // 請求失敗,創(chuàng)建一個(gè)錯(cuò)誤對象芹血,返回錯(cuò)誤文本
        rejects(new Error(this.statusText))
      }
    }
    // 開始執(zhí)行異步請求
    xhr.send()
  })
}

ajax('/api/user.json').then((res) => {
  console.log(res)
}, (error) => {
  console.log(error)
})

Promise的本質(zhì)

本質(zhì)上也是使用回調(diào)函數(shù)的方式去定義異步任務(wù)結(jié)束后所需要執(zhí)行的任務(wù)贮泞。這里的回調(diào)函數(shù)是通過then方法傳遞過去的

Promise鏈?zhǔn)秸{(diào)用

常見誤區(qū)

  • 嵌套使用的方式是使用Promise最常見的誤區(qū)。要使用promise的鏈?zhǔn)秸{(diào)用的方法盡可能保證異步任務(wù)的扁平化幔烛。

鏈?zhǔn)秸{(diào)用的理解

  • promise對象then方法隙畜,返回了全新的promise對象∷当矗可以再繼續(xù)調(diào)用then方法议惰,如果return的不是promise對象,而是一個(gè)值乡恕,那么這個(gè)值會作為resolve的值傳遞言询,如果沒有值,默認(rèn)是undefined
  • 后面的then方法就是在為上一個(gè)then返回的Promise注冊回調(diào)
  • 前面then方法中回調(diào)函數(shù)的返回值會作為后面then方法回調(diào)的參數(shù)
  • 如果回調(diào)中返回的是Promise傲宜,那后面then方法的回調(diào)會等待它的結(jié)束

Promise.prototype.then()

promise對象就可以調(diào)用.then()运杭,是promise原型對象上的方法

promise.then(onFulfilled,onRejected);

onFulfilled 參數(shù)對應(yīng) resolve,處理結(jié)果值函卒,必選

onRejected 參數(shù)對應(yīng) reject辆憔,Error對象,可選

Promise 對象會在變?yōu)?resolve 或者 reject 的時(shí)候分別調(diào)用相應(yīng)注冊的回調(diào)函數(shù)报嵌。

  • 當(dāng) handler 返回一個(gè)正常值的時(shí)候虱咧,這個(gè)值會傳遞給 Promise 對象的 onFulfilled 方法。
  • 定義的 handler 中產(chǎn)生異常的時(shí)候锚国,這個(gè)值則會傳遞給 Promise 對象的 onRejected 方法腕巡。

這兩個(gè)參數(shù)都是兩個(gè)函數(shù)類型,如果這兩個(gè)參數(shù)是非函數(shù)或者被遺漏血筑,就忽略掉這兩個(gè)參數(shù)了绘沉,返回一個(gè)空的promise對象。

// 普通的寫法會導(dǎo)致有不穩(wěn)定輸出
function loadScript (src) {
    //resolve, reject是可以改變Promise狀態(tài)的豺总,Promise的狀態(tài)是不可逆的
    return new Promise((resolve, reject) => {
        let script = document.createElement('script')
        script.src = src
        script.onload = () => resolve(src) //fulfilled,result
        script.onerror = (err) => reject(err) //rejected,error
        document.head.append(script)
    })
}

loadScript('./1.js')
    .then(loadScript('./2.js'))
    .then(loadScript('./3.js'))
    
//不穩(wěn)定輸出    
// 1
// 2
// 3
//----------------------------------------------------------------------------
// 如果把加載2和3的放在1的then方法中
function loadScript (src) {
    //resolve, reject是可以改變Promise狀態(tài)的车伞,Promise的狀態(tài)是不可逆的
    return new Promise((resolve, reject) => {
        let script = document.createElement('script')
        script.src = src
        script.onload = () => resolve(src) //fulfilled,result
        script.onerror = (err) => reject(err) //rejected,error
        document.head.append(script)
    })
}

loadScript('./1.js')
    .then(() => {
        loadScript('./2.js')
    }, (err) => {
        console.log(err)
    }).then( () => {
        loadScript('./3.js')
    }, (err) => {
        console.log(err)
    })
    
// 穩(wěn)定輸出
// 1
// 不穩(wěn)定輸出
// 2
// 3
// ----------------------------------------------
//但是如果中間有錯(cuò)誤的時(shí)候,下面的3還是會執(zhí)行喻喳。
loadScript('./1.js')
    .then(() => {
        loadScript('./4.js')
    }, (err) => {
        console.log(err)
    }).then( () => {
        loadScript('./3.js')
    }, (err) => {
        console.log(err)
    })

// 1
// 報(bào)錯(cuò)
// 3
// 不符合題意另玖,如果是報(bào)錯(cuò)之后,3不應(yīng)該執(zhí)行
// -------------------------------------------------------
loadScript('./1.js')
    .then(() => {
        return loadScript('./2.js')
    }, (err) => {
        console.log(err)
    }).then(() => {
        return loadScript('./3.js')
    }, (err) => {
        console.log(err)
    })
// 不加返回值,依舊是一個(gè)空的promise對象日矫,無法用resolve, reject影響下一步.then()的執(zhí)行
// 添加返回值之后就可以穩(wěn)定輸出
// 1
// 2
// 3

Promise異常處理

異常處理有以下幾種方法:

then中回調(diào)的onRejected方法

Promise.prototype.catch()(推薦)

catch是promise原型鏈上的方法赂弓,用來捕獲reject拋出的一場绑榴,進(jìn)行統(tǒng)一的錯(cuò)誤處理哪轿,使用.catch方法更為常見,因?yàn)楦臃湘準(zhǔn)秸{(diào)用翔怎。

p.catch(onRejected);

ajax('/api/user.json')
  .then(function onFulfilled(res) {
    console.log('onFulfilled', res)
  }).catch(function onRejected(error) {
    console.log('onRejected', error)
  })
  
// 相當(dāng)于
ajax('/api/user.json')
  .then(function onFulfilled(res) {
    console.log('onFulfilled', res)
  })
  .then(undefined, function onRejected(error) {
    console.log('onRejected', error)
  })

.catch形式和前面then里面的第二個(gè)參數(shù)的形式窃诉,兩者異常捕獲的區(qū)別:

  • .catch()是對上一個(gè).then()返回的promise進(jìn)行處理,不過第一個(gè)promise的報(bào)錯(cuò)也順延到了catch
  • then的第二個(gè)參數(shù)形式赤套,只能捕獲第一個(gè)promise的報(bào)錯(cuò)飘痛,如果當(dāng)前thenresolve函數(shù)處理中有報(bào)錯(cuò)是捕獲不到的。

所以.catch是給整個(gè)promise鏈條注冊的一個(gè)失敗回調(diào)容握。推薦使用P觥!L奘稀塑猖!

function loadScript (src) {
    //resolve, reject是可以改變Promise狀態(tài)的,Promise的狀態(tài)是不可逆的
    return new Promise((resolve, reject) => {
        let script = document.createElement('script')
        script.src = src
        script.onload = () => resolve(src) //fulfilled,result
        script.onerror = (err) => reject(err) //rejected,error
        document.head.append(script)
    })
}


loadScript('./1.js')
    .then(() => {
        return loadScript('./2.js')
    }).then(() => {
        return loadScript('./3.js')
    })
    .catch(err => {
        console.log(err)
    })
// throw new Error 不要用這個(gè)方法谈跛,要用catch和reject羊苟,去改變promise的狀態(tài)的方式    

全局對象上的unhandledrejection事件

還可以在全局對象上注冊一個(gè)unhandledrejection事件,處理那些代碼中沒有被手動捕獲的promise異常感憾,當(dāng)然并不推薦使用蜡励。

更合理的是:在代碼中明確捕獲每一個(gè)可能的異常,而不是丟給全局處理

// 瀏覽器
window.addEventListener('unhandledrejection', event => {
  const { reason, promise } = event
  console.log(reason, promise)

  //reason => Promise 失敗原因阻桅,一般是一個(gè)錯(cuò)誤對象
  //promise => 出現(xiàn)異常的Promise對象

  event.preventDefault()
}, false)

// node
process.on('unhandledRejection', (reason, promise) => {
  console.log(reason, promise)

  //reason => Promise 失敗原因凉倚,一般是一個(gè)錯(cuò)誤對象
  //promise => 出現(xiàn)異常的Promise對象
})

Promise靜態(tài)方法

類型轉(zhuǎn)換 —— Promise.resolve()

靜態(tài)方法 Promise.resolve(value) 可以認(rèn)為是 new Promise() 方法的快捷方式。

Promise.resolve(42)
//等同于
new Promise(function (resolve) {
  resolve(42)
})

如果接受的是一個(gè)promise對象嫂沉,那么這個(gè)對象會原樣返回

const promise2 = Promise.resolve(promise)
console.log(promise === promise2) // true

如果傳入的是一個(gè)對象占遥,且這個(gè)對象也有一個(gè)then方法,傳入成功和失敗的回調(diào)输瓜,那么在后面執(zhí)行的時(shí)候瓦胎,也是可以按照promisethen來拿到。

(這個(gè)then方法尤揣,實(shí)現(xiàn)了一個(gè)thenable的接口搔啊,即可以被then的對象)

使用場景

  1. 可以是把第三方模擬promise庫轉(zhuǎn)化成promise對象
Promise.reslove({
    then: function(onFulfilled, onRejected) {
        onFulfilled('foo')
    }
})
.then(function (value) {
    console.log(value) // foo
})
  1. 直接將數(shù)值轉(zhuǎn)換成promise對象返回
function test (bool) {
    if (bool) {
        return new Promise((resolve,reject) => {
            resolve(30) 
        })
    } else {
        return Promise.resolve(42)
    }
}
test(1).then((value) => {
    console.log(value)
})

Promise.reject()

Promise.reject(error) 是和 Promise.resolve(value) 類似的靜態(tài)方法,是 new Promise() 方法的快捷方式北戏。

創(chuàng)建一個(gè)一定是失敗的promise對象

Promise.reject(new Error('出錯(cuò)了'))
//等同于
new Promise(function (resolve) {
  reject(new Error('出錯(cuò)了'))
})

數(shù)據(jù)聚合 —— Promise.all()

如果需要同時(shí)進(jìn)行多個(gè)異步任務(wù)负芋,使用promise靜態(tài)方法中的all方法,可以把多個(gè)promise合并成一個(gè)promise統(tǒng)一去管理。

Promise.all(promiseArray);

  • Promise.all 生成并返回一個(gè)新的 Promise 對象旧蛾,所以它可以使用 Promise 實(shí)例的所有方法莽龟。參數(shù)傳遞promise數(shù)組中所有的 Promise 對象都變?yōu)?code>resolve的時(shí)候,該方法才會返回锨天, 新創(chuàng)建的 Promise 則會使用這些 promise 的值毯盈。

  • 參數(shù)是一個(gè)數(shù)組,元素可以是普通值病袄,也可以是一個(gè)promise對象搂赋,輸出順序和執(zhí)行順序有關(guān),

  • 該函數(shù)生成并返回一個(gè)新的 Promise 對象益缠,所以它可以使用 Promise 實(shí)例的所有方法脑奠。參數(shù)傳遞promise數(shù)組中所有的 Promise 對象都變?yōu)?code>resolve的時(shí)候,該方法才會返回完成幅慌。只要有一個(gè)失敗宋欺,就會走catch

  • 由于參數(shù)數(shù)組中的每個(gè)元素都是由 Promise.resolve 包裝(wrap)的胰伍,所以Paomise.all 可以處理不同類型的 promose 對象齿诞。

var promise = Promise.all([
    // ajax函數(shù)是一個(gè)異步函數(shù)并返回promise,不需要關(guān)心哪個(gè)結(jié)果先回來喇辽,因?yàn)槭嵌纪瓿芍笳喜僮?    ajax('/api/users.json'),
    ajax('/api/posts.json')
])

Promise.then(function(values) {
    console.log(values) //返回的是一個(gè)數(shù)組掌挚,每個(gè)數(shù)組元素對應(yīng)的是其promise的返回結(jié)果
}).catch(function(error) {
    console.log(error) // 只要有一個(gè)失敗,那么就會整體失敗走到catch里面
})

競爭 —— Promise.race()

Promise.race(promiseArray);

all一樣會接收一個(gè)數(shù)組菩咨,元素可以是普通值也可以是promise對象吠式,和all不同的是,它只會等待第一個(gè)結(jié)束的任務(wù) 抽米。

// 下面的例子如果request超過了500ms特占,那么就會報(bào)超時(shí)錯(cuò)誒,如果小于500ms云茸,則正常返回是目。
const request = ajax('/api/posts.json')
const timeout = new Promise((resovle, reject) => {
    setTimeout(() => reject(new Error('timeout')), 500)
})

Promise.race([
    request,
    timeout
])
.then(value => {
    console.log(value)
})
.catch(error => {
    console.log(error)
})

Promise執(zhí)行時(shí)序 —— 宏任務(wù) vs 微任務(wù)

執(zhí)行順序 : 宏任務(wù) => 微任務(wù) => 宏任務(wù)

微任務(wù)promise之后才加入進(jìn)去的,目的是為了提高整體的響應(yīng)能力

我們目前絕大多數(shù)異步調(diào)用都是作為宏任務(wù)執(zhí)行标捺,promise的回調(diào) & MutationObserver & node中的process.nextTick會作為微任務(wù)執(zhí)行

下面的例子懊纳,當(dāng)前宏任務(wù)立即執(zhí)行,then是微任務(wù)會延后執(zhí)行亡容,setTImeout是異步的一個(gè)宏任務(wù)也會延后執(zhí)行嗤疯。當(dāng)前宏任務(wù)執(zhí)行完畢之后,微任務(wù)會先執(zhí)行完畢之后下一個(gè)宏任務(wù)才會執(zhí)行闺兢。

console.log('global start')

setTimeout(() => {
    console.log('setTimeout')
}, 0)
Promise.resolve()
    .then(( => {
        console.log('promise')
    }))
    .then(( => {
        console.log('promise2')
    }))
    .then(( => {
        console.log('promise3')
    }))

console.log('global end')

// global start
// global end
// promise
// promise2
// promise3
// setTimeout

具體的牽扯到eventLoop的東西之后再進(jìn)一步探討茂缚。

深度剖析:手寫一個(gè)Promise源碼

深度剖析:手寫一個(gè)Promise源碼

ES6-ES10學(xué)習(xí)版圖

說實(shí)話這個(gè)是最近比較復(fù)雜的一個(gè)筆記了,給自己點(diǎn)個(gè)贊,標(biāo)個(gè)特殊標(biāo)記脚囊。


image
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末龟糕,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子悔耘,更是在濱河造成了極大的恐慌讲岁,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,252評論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件淮逊,死亡現(xiàn)場離奇詭異催首,居然都是意外死亡扶踊,警方通過查閱死者的電腦和手機(jī)泄鹏,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,886評論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來秧耗,“玉大人备籽,你說我怎么就攤上這事》志” “怎么了车猬?”我有些...
    開封第一講書人閱讀 168,814評論 0 361
  • 文/不壞的土叔 我叫張陵,是天一觀的道長尺锚。 經(jīng)常有香客問我珠闰,道長,這世上最難降的妖魔是什么瘫辩? 我笑而不...
    開封第一講書人閱讀 59,869評論 1 299
  • 正文 為了忘掉前任伏嗜,我火速辦了婚禮,結(jié)果婚禮上伐厌,老公的妹妹穿的比我還像新娘承绸。我一直安慰自己,他們只是感情好挣轨,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,888評論 6 398
  • 文/花漫 我一把揭開白布军熏。 她就那樣靜靜地躺著,像睡著了一般卷扮。 火紅的嫁衣襯著肌膚如雪荡澎。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,475評論 1 312
  • 那天晤锹,我揣著相機(jī)與錄音摩幔,去河邊找鬼。 笑死抖甘,一個(gè)胖子當(dāng)著我的面吹牛热鞍,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 41,010評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼薇宠,長吁一口氣:“原來是場噩夢啊……” “哼偷办!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起澄港,我...
    開封第一講書人閱讀 39,924評論 0 277
  • 序言:老撾萬榮一對情侶失蹤椒涯,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后回梧,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體废岂,經(jīng)...
    沈念sama閱讀 46,469評論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,552評論 3 342
  • 正文 我和宋清朗相戀三年狱意,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了湖苞。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,680評論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡详囤,死狀恐怖财骨,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情藏姐,我是刑警寧澤隆箩,帶...
    沈念sama閱讀 36,362評論 5 351
  • 正文 年R本政府宣布,位于F島的核電站羔杨,受9級特大地震影響捌臊,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜兜材,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,037評論 3 335
  • 文/蒙蒙 一理澎、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧护姆,春花似錦矾端、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,519評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至灯变,卻和暖如春殴玛,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背添祸。 一陣腳步聲響...
    開封第一講書人閱讀 33,621評論 1 274
  • 我被黑心中介騙來泰國打工滚粟, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人刃泌。 一個(gè)月前我還...
    沈念sama閱讀 49,099評論 3 378
  • 正文 我出身青樓凡壤,卻偏偏與公主長得像署尤,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子亚侠,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,691評論 2 361