啊,似乎沒有真正理解 try...catch...finally锻狗!

配圖源自 Freepik

寫了那么久的 JavaScript满力,似乎真的沒有很認(rèn)真地去了解 try...catch...finally 的各種用法,真是慚愧了轻纪!Anyway油额,不懂就學(xué)...

一、錯(cuò)誤與異常

錯(cuò)誤刻帚,在程序中是很常見的潦嘶。它可以是 JS 引擎在執(zhí)行代碼時(shí)內(nèi)部拋出的,也可以是代碼開發(fā)人員針對一些不合法的輸入而主動(dòng)拋出的崇众,或者是網(wǎng)絡(luò)斷開連接導(dǎo)致的錯(cuò)誤等等...

可能很多人會(huì)認(rèn)為掂僵,「錯(cuò)誤」和「異常」是同一回事顷歌,其實(shí)不然蚜退,一個(gè)錯(cuò)誤對象只有在被拋出時(shí)才成為異常覆致。

1.1 錯(cuò)誤

在 JavaScript 中,錯(cuò)誤通常是指 Error 實(shí)例對象或 Error 的派生類實(shí)例對象(比如 TypeError芥备、ReferenceError溪窒、SyntaxError 等等)坤塞。創(chuàng)建 Error 實(shí)例對象很簡單冯勉,如下:

const error = new Error('oops') // 等價(jià)于 Error('oops')
const typeError = new TypeError('oops')
// ...

雖然 Error 及其派生類是構(gòu)造函數(shù),但是當(dāng)作函數(shù)調(diào)用也是允許的(即省略 new 關(guān)鍵字)摹芙,同樣會(huì)返回一個(gè)錯(cuò)誤實(shí)例對象灼狰。

一個(gè)錯(cuò)誤實(shí)例對象,包含以下屬性和方法:

const errorInstance = {
  name: String, // 標(biāo)準(zhǔn)屬性浮禾,所有瀏覽器均支持(默認(rèn)值為構(gòu)造方法名稱)
  message: String, // 標(biāo)準(zhǔn)屬性交胚,所有瀏覽器均支持(默認(rèn)值為空字符串,實(shí)例化時(shí)傳入的第一個(gè)參數(shù)可修改其屬性值)
  stack: String, // 非標(biāo)準(zhǔn)屬性盈电,但所有瀏覽器均支持(棧屬性蝴簇,可以追蹤發(fā)生錯(cuò)誤的具體信息)

  columnNumber: Number, // 非標(biāo)準(zhǔn)屬性,僅 Firefox 瀏覽器支持(列號)
  lineNumber: Number, // 非標(biāo)準(zhǔn)屬性匆帚,僅 Firefox 瀏覽器支持(行號)
  fileName: String, // 非標(biāo)準(zhǔn)屬性熬词,僅 Firefox 瀏覽器支持(文件路徑)

  column: Number, // 非標(biāo)準(zhǔn)屬性,僅 Safari 瀏覽器支持(同上述三個(gè)屬性)
  line: Number, // 非標(biāo)準(zhǔn)屬性吸重,僅 Safari 瀏覽器支持
  sourceURL: String, // 非標(biāo)準(zhǔn)屬性互拾,僅 Safari 瀏覽器支持

  toString: Function, // 標(biāo)準(zhǔn)方法(其返回值是 name 和 message 屬性的字符串表示)
}

我們寫個(gè)最簡單的示例,打印看下各大瀏覽器的情況:

try {
  throw new TypeError('oops')
} catch (e) {
  console.log(e.toString())
  console.dir(e)
}

插個(gè)題外話:

不知道有人沒有對此有疑惑的嚎幸,為什么 console.log() 一個(gè) Error 對象颜矿,打印出來的是字符串,而不是一個(gè)對象呢嫉晶?

const err = new Error('wrong')
console.log(err) // "Error: wrong"
console.log(typeof err) // "object"

那么骑疆,如果想打印出 Error 對象,使用 console.dir() 即可车遂。

前面 console.log() 打印結(jié)果為字符串的原因其實(shí)很簡單封断,那就是 console.log() 內(nèi)部「偷偷地」做了一件事,當(dāng)傳入的實(shí)參為 Error 對象(或其派生類錯(cuò)誤對象)舶担,它會(huì)先調(diào)用 Error 對象的 Error.prototype.toString() 方法坡疼,然后將其結(jié)果輸出到控制臺(tái),所以我們看到的打印結(jié)果為字符串衣陶。

其實(shí)現(xiàn)如下:

// polyfill
Error.prototype.toString = function () {
  'use strict'

  var obj = Object(this)
  if (obj !== this) throw new TypeError()

  var name = this.name
  name = name === undefined ? 'Error' : String(name)

  var msg = this.message
  msg = msg === undefined ? '' : String(msg)

  if (name === '') return msg
  if (msg === '') return name

  return name + ': ' + msg
}

細(xì)心的同學(xué)會(huì)發(fā)現(xiàn)柄瑰,在不同瀏覽器下,其打印結(jié)果可能會(huì)不相同(但不重要)剪况。原因也非常簡單教沾,console 并不是 ECMAScript 標(biāo)準(zhǔn),而是瀏覽器 BOM 對象提供的一個(gè)接口译断,其標(biāo)準(zhǔn)由 WHATWG 機(jī)構(gòu)制定授翻,雖然標(biāo)準(zhǔn)是統(tǒng)一的,但實(shí)現(xiàn)的是各瀏覽器廠商大爺們,它們有可能不會(huì)嚴(yán)格遵守規(guī)范去實(shí)現(xiàn)堪唐,因而產(chǎn)生差異化巡语。比如,此前寫過一篇文章是關(guān)于不同宿主環(huán)境下 async/await 和 promise 執(zhí)行順序的差異淮菠,就因?yàn)?JS 引擎實(shí)現(xiàn)差異導(dǎo)致的男公。

1.2 異常

前面提到,當(dāng)錯(cuò)誤被拋出時(shí)就會(huì)成為異常合陵。

假設(shè)我們編寫的代碼存在語法錯(cuò)誤枢赔,那么在編譯階段的語法分析過程就會(huì)被聰明的 JS 引擎發(fā)現(xiàn),因而在編譯階段便會(huì)拋出 SyntaxError拥知。

假設(shè)我們代碼沒有語法錯(cuò)誤踏拜,但錯(cuò)誤地引用了一個(gè)不存在的變量,那么在執(zhí)行階段的執(zhí)行上下文過程(代碼執(zhí)行之前的一個(gè)過程)举庶,聰明的 JS 引擎發(fā)現(xiàn)在其作用域鏈上找不到該變量执隧,那么就會(huì)拋出 ReferenceError。

假設(shè)即不存在語法錯(cuò)誤户侥,也沒有引用錯(cuò)誤镀琉,但我們對一個(gè)變量做了“不合法”的操作,比如 null.name蕊唐、'str'.push('ing')屋摔,那么 JS 引擎就會(huì)拋出 TypeError。

還有很多很多替梨,就不舉例了钓试。

前面都是 JS 引擎主動(dòng)拋出的錯(cuò)誤,那么副瀑,我們開發(fā)者則可通過 throw 關(guān)鍵字來拋出錯(cuò)誤弓熏,語法很簡單:

// throw expression
throw 123
throw 'abc'
throw { name: 'Frankie' }
// ...

請注意,在 JavaScript 中 throw 關(guān)鍵字和 return糠睡、break挽鞠、continue 等關(guān)鍵字一樣,會(huì)受到 ASI(Automatic Semicolon Insertion)規(guī)則的影響狈孔,它不能在 throwexpression 之間插入任意換行符信认,否則可能得不到預(yù)期結(jié)果。

語法很簡單均抽,但通常項(xiàng)目中「不建議」直接拋出一個(gè)字面量嫁赏,而是拋出 Error 對象或其派生類對象,應(yīng)該這樣:

throw new Error('oops')
throw new TypeError('arguments must be a number.')
// ...

原因是 Error 對象會(huì)記錄引發(fā)此錯(cuò)誤的文件的路徑油挥、行號潦蝇、列號等信息款熬,這應(yīng)該是排除錯(cuò)誤最有效的信息。在 ESLint 中的 no-throw-literal 規(guī)則护蝶,正是用來約束上述直接拋出字面量的寫法的华烟。

除了 throw 關(guān)鍵字之外,ES6 中強(qiáng)大的 Generator 函數(shù)也提供了一個(gè)可拋出異常的方法:Generator.prototype.throw()持灰。它可以在函數(shù)體外拋出異常,然后在函數(shù)體內(nèi)捕獲異常负饲。

function* genFn() {
  try {
    yield 1
  } catch (e) {
    console.log('inner -->', e)
  }
}

try {
  const gen = genFn()
  gen.next()
  gen.throw(new Error('oops'))
} catch (e) {
  console.log('outer -->', e)
}

打印結(jié)果是 inner --> Error: oops堤魁。如果生成器函數(shù)體內(nèi)沒有 try...catch 去捕獲異常,那么它所拋出的異撤凳可以被外部的 try...catch 語句捕獲到妥泉。

當(dāng)生成器「未開始執(zhí)行之前」或者「執(zhí)行結(jié)束之后」,調(diào)用生成器的 throw() 方法洞坑。它的異常只會(huì)被生成器函數(shù)外部的 try...catch 捕獲到盲链。若外部沒有 try...catch 語句,則會(huì)報(bào)錯(cuò)且代碼就會(huì)停止執(zhí)行迟杂。詳看

需要注意的是刽沾,生成器函數(shù)雖然是一個(gè)很強(qiáng)大的異步編程的解決方案,但它本身是同步的排拷,而且執(zhí)行生成器函數(shù)并不會(huì)立刻執(zhí)行函數(shù)體的邏輯侧漓,它需要主動(dòng)調(diào)用生成器實(shí)例對象的 next()return()监氢、throw() 方法去執(zhí)行函數(shù)體內(nèi)的代碼布蔗。當(dāng)然,你也可以通過 for...of浪腐、解構(gòu)等語法去遍歷它纵揍,因?yàn)樯善鞅旧砭褪且粋€(gè)可迭代對象。

二议街、try...catch

對于可能存在異常的代碼泽谨,我們通常會(huì)使用 try...catch...finally 去處理一些可預(yù)見或不可預(yù)見的錯(cuò)誤。語法有以下三種形式:

  • try...catch
  • try...finally
  • try...catch...finally

且必須至少存在一個(gè) catch 塊或 finally 塊傍睹。

try {
  throw new Error('oops')
} catch (e) {
  // some statements...
}

以上這些語法隔盛,寫過 JavaScript 相信都懂。

曾經(jīng) Firefox 59 及以下版本的瀏覽器拾稳,有一種 Conditional catch-blocks 的「條件 catch 子句」的語法(請注意吮炕,其他瀏覽器并不支持該語法,即便是遠(yuǎn)古神器 IE5访得,因此知道有這回事就行了)龙亲。它的語法如下:

try {
  // may throw three types of exceptions
  willThrowError()
} catch (e if e instanceof TypeError) {
  // statements to handle TypeError exceptions
} catch (e if e instanceof RangeError) {
  // statements to handle RangeError exceptions
} catch (e if e instanceof EvalError) {
  // statements to handle EvalError exceptions
} catch (e) {
  // statements to handle any unspecified exceptions
}

那么符合 ECMAScript 標(biāo)準(zhǔn)的「條件 catch 子句」應(yīng)該這樣寫:

try {
  // may throw three types of exceptions
  willThrowError()
} catch (e) {
  if (e instanceof TypeError) {
    // statements to handle TypeError exceptions
  } else if (e instanceof RangeError) {
    // statements to handle RangeError exceptions
  } else if (e instanceof EvalError) {
    // statements to handle EvalError exceptions
  } else {
    // statements to handle any unspecified exceptions
  }
}

請注意陕凹,try...catch 只能以「同步」的形式處理異常,因此對于 XHR鳄炉、Fetch API杜耙、Promise 等異步處理是無法捕獲其錯(cuò)誤的,究其原因就是 Event Loop 嘛拂盯。當(dāng)然實(shí)際中可能結(jié)合 async/await 來控制會(huì)更多一些佑女。

2.1 catch子句

我們知道,若 try 塊中拋出異常時(shí)谈竿,會(huì)立即轉(zhuǎn)至 catch 子句執(zhí)行团驱。若 try 塊中沒有異常拋出,會(huì)跳過 catch 子句空凸。

try {
  // try statements
} catch (exception_var) {
  // catch statements
}

其中 exception_var 表示異常標(biāo)識符(如 catch(e) 中的 e)嚎花,它是「可選」的,因此可以這樣編寫 try { ... } catch { ... }呀洲。通過該標(biāo)識符我們可以獲取關(guān)于被拋出異常的信息紊选。

請注意,該標(biāo)識符的「作用域」僅在 catch 塊中有效道逗。當(dāng)進(jìn)入 catch 子句時(shí)兵罢,它被創(chuàng)建,當(dāng) catch 子句執(zhí)行完畢憔辫,此標(biāo)識符將不可再用趣些。也可以理解為(在 ES6 以前)異常標(biāo)識符是 JavaScript 中含有“塊級作用域”的變量。

2.2 finally 子句

finally 子句在 try 塊和 catch 塊之后執(zhí)行贰您,但在下一個(gè) try 聲明之前執(zhí)行坏平。無論是否異常拋出,finally 子句總是會(huì)執(zhí)行锦亦。

如果從 finally 塊中返回一個(gè)值舶替,那么這個(gè)值將成為整個(gè) try...catch...finally 的返回值,無論是否有 return 語句在 trycatch 塊中(即使 catch 塊中拋出了異常)杠园。

對于這個(gè)我表示很無語顾瞪,可能整個(gè)前端圈子就我還不知道吧,原來 finally 還能 return 一個(gè)值抛蚁,在做項(xiàng)目的過程中陈醒,確實(shí)沒寫過和見過在 finallyreturn 某個(gè)值的,讓您見笑了瞧甩,實(shí)在慚愧钉跷。

但請注意,若要在 try...catch...finally 中使用 return肚逸,它只能在函數(shù)中運(yùn)行爷辙,否則是不允許的彬坏,會(huì)拋出語法錯(cuò)誤。

try {
  doSomething()
} catch (e) {
  console.warn(e)
  throw e
} finally {
  return 'completed' // SyntaxError: Illegal return statement
}

2.3 執(zhí)行順序

在平常的項(xiàng)目中膝晾,一般的 try...catch 寫法是在 try 塊中 return栓始,catch 塊則作相應(yīng)的異常處理,少數(shù)情況也會(huì)在 catch 塊中 return血当。因此幻赚,大家對這種常規(guī)寫法的執(zhí)行順序應(yīng)該沒什么問題。

先來個(gè)誰都會(huì)的示例:

function foo() {
  try {
    console.log('try statement')
    throw new Error('oops')
  } catch (e) {
    console.log('catch statement')
    return 'fail'
  }
}

foo()
// 以上歹颓,先后打印 "try statement"坯屿、"catch statement",foo 函數(shù)返回一個(gè) "fail" 值

接著再看巍扛,它打印什么,函數(shù)又返回什么呢乏德?

function foo() {
  try {
    console.log('try statement')
    throw new Error('oops')
  } catch (e) {
    console.log('catch statement')
    return 'fail'
  } finally {
    console.log('finally statement')
    return 'complete'
  }
}

foo()
// 先后打映芳椤:"try statement"、"catch statement"喊括、"finally statement"
// foo 函數(shù)返回值是 "complete"

前面提到胧瓜,如果 finally 塊中含有 return 語句,那么它的 return 值將作為當(dāng)前函數(shù)的返回值郑什,因此 foo() 結(jié)果為 "complete"府喳。

然后我們再稍微改動(dòng)一下,在 try 塊中 return 一個(gè)值蘑拯,看下結(jié)果又有什么不同钝满?

function foo() {
  try {
    console.log('try statement')
    return 'success'
  } catch (e) {
    console.log('catch statement')
    return 'fail'
  } finally {
    console.log('finally statement')
    return 'complete'
  }
}

foo()
// 先后打印:"try statement"申窘、"finally statement"
// foo 函數(shù)返回值是 "complete"

由于 try 塊中沒有拋出異常弯蚜,因此 catch 塊會(huì)被跳過,不執(zhí)行剃法,但是 finally 塊還是會(huì)執(zhí)行的碎捺,而且它里面返回了 "complete",因此這個(gè)值也就作為 foo 函數(shù)的返回值了贷洲。

因此收厨,我們大致可以得出一個(gè)結(jié)論,finally 塊的代碼總會(huì)在 return 之前執(zhí)行优构,不管 return 是存在于 try诵叁、catch 還是 finally 塊中。

但是俩块,這就完了嗎黎休?

還沒有浓领,我們再看一個(gè)示例,看看里面這個(gè) bar() 函數(shù)是惰性求值势腮?還是怎樣联贩?

function foo() {
  try {
    console.log('try statement')
    throw new Error('oops')
  } catch (e) {
    console.log('catch statement')
    return bar()
  } finally {
    console.log('finally statement')
    return 'complete'
  }
}

function bar() {
  console.log('bar statement')
  return 'something'
}

foo()

以上示例,打印順序和結(jié)果是什么呢捎拯?

// 打印順序泪幌,依次是:
"try statement"
"catch statement"
"bar statement"
"finally statement"

// 結(jié)果是 "complete"

假設(shè) catch 塊中的 return bar() 換成 throw bar() 呢,結(jié)果又有什么變化呢署照?如果換成這個(gè)你就猶豫了祸泪,說明你理解得不夠深刻,因此這里我不給出答案建芙,你自己去試試没隘,效果更佳!

綜上所述禁荸,finally 塊的執(zhí)行時(shí)機(jī)如下:

在所有 try 塊和 catch 塊(如果有右蒲,且觸發(fā)進(jìn)入的話)執(zhí)行完之后,即便此時(shí) try 塊或 catch 塊中存在 returnthrow 語句赶熟,它們將會(huì)被 Hold 住先不返回或拋出異常瑰妄,繼續(xù)執(zhí)行 finally 塊中的代碼:

  • 如果 finally 中存在 return 語句,其返回值將作為整個(gè)函數(shù)的返回值(前面 try 塊或 catch 中的 returnthrow 都會(huì)被忽略映砖,可以理解為沒有了 returnthrow 關(guān)鍵字一樣)间坐。
  • 如果 finally 中存在 throw 語句,前面 try 塊或 catch 中的 returnthrow 同樣會(huì)被忽略邑退,最后整個(gè)函數(shù)將會(huì)拋出 finally 塊中的異常竹宋。

2.4 嵌套使用

它是可以嵌套使用的,當(dāng)內(nèi)部的 try...catch...finally 中拋出異常瓜饥,它會(huì)被離它最近的 catch 塊捕獲到逝撬。

function foo() {
  try {
    try {
      return 'success'
    } finally {
      throw new Error('inner oops') // 它將會(huì)被外層的 catch 塊所捕獲到
    }
  } catch (e) {
    console.log(e) // Error: inner oops
  }
}

foo()

注意,本節(jié)內(nèi)容所述都是同步代碼乓土,而不存在任何異步代碼宪潮。

到此,已徹底弄懂 try...catch...finally 語句了趣苏,再也不慌了狡相!

三、異常有哪些食磕?

在 Web 中尽棕,主要有以下幾種異常類型:

  • JavaScript 異常
  • DOM 和 BOM 異常
  • 網(wǎng)絡(luò)資源加載異常
  • Script Error
  • 網(wǎng)頁異常

3.1 JavaScript 異常

try...catch 可以捕獲同步任務(wù)導(dǎo)致的異常,也可以捕獲 async/await 中的異常彬伦。

Promise 中拋出的異常滔悉,則可通過 Promise.prototype.catch()Promise.prototype.then(onResolved, onRejected) 捕獲伊诵。

3.2 DOM Exception

在調(diào)用 DOM API 時(shí)發(fā)生的,都屬于 DOM Exception回官。比如:

<!DOCTYPE html>
<html>
  <body>
    <video id="video" controls src="https://dl.ifanr.cn/hydrogen/landing-page/ifanr-products-introduce-v1.1.mp4"></video>
    <script>
      window.onload = function () {
        const video = document.querySelector('#video')
        video.play() // Uncaught (in promise) DOMException: play() failed because the user didn't interact with the document first.
      }
    </script>
  </body>
</html>

未完待續(xù)...

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末曹宴,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子歉提,更是在濱河造成了極大的恐慌笛坦,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,123評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件苔巨,死亡現(xiàn)場離奇詭異版扩,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)侄泽,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,031評論 2 384
  • 文/潘曉璐 我一進(jìn)店門礁芦,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人悼尾,你說我怎么就攤上這事宴偿。” “怎么了诀豁?”我有些...
    開封第一講書人閱讀 156,723評論 0 345
  • 文/不壞的土叔 我叫張陵,是天一觀的道長窥妇。 經(jīng)常有香客問我舷胜,道長,這世上最難降的妖魔是什么活翩? 我笑而不...
    開封第一講書人閱讀 56,357評論 1 283
  • 正文 為了忘掉前任烹骨,我火速辦了婚禮,結(jié)果婚禮上材泄,老公的妹妹穿的比我還像新娘沮焕。我一直安慰自己,他們只是感情好拉宗,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,412評論 5 384
  • 文/花漫 我一把揭開白布峦树。 她就那樣靜靜地躺著,像睡著了一般旦事。 火紅的嫁衣襯著肌膚如雪魁巩。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,760評論 1 289
  • 那天姐浮,我揣著相機(jī)與錄音谷遂,去河邊找鬼。 笑死卖鲤,一個(gè)胖子當(dāng)著我的面吹牛肾扰,可吹牛的內(nèi)容都是我干的畴嘶。 我是一名探鬼主播,決...
    沈念sama閱讀 38,904評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼集晚,長吁一口氣:“原來是場噩夢啊……” “哼窗悯!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起甩恼,我...
    開封第一講書人閱讀 37,672評論 0 266
  • 序言:老撾萬榮一對情侶失蹤蟀瞧,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后条摸,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體悦污,經(jīng)...
    沈念sama閱讀 44,118評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,456評論 2 325
  • 正文 我和宋清朗相戀三年钉蒲,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了切端。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,599評論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡顷啼,死狀恐怖踏枣,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情钙蒙,我是刑警寧澤茵瀑,帶...
    沈念sama閱讀 34,264評論 4 328
  • 正文 年R本政府宣布,位于F島的核電站躬厌,受9級特大地震影響马昨,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜扛施,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,857評論 3 312
  • 文/蒙蒙 一鸿捧、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧疙渣,春花似錦匙奴、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,731評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至懦冰,卻和暖如春灶轰,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背刷钢。 一陣腳步聲響...
    開封第一講書人閱讀 31,956評論 1 264
  • 我被黑心中介騙來泰國打工笋颤, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,286評論 2 360
  • 正文 我出身青樓伴澄,卻偏偏與公主長得像赋除,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子非凌,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,465評論 2 348

推薦閱讀更多精彩內(nèi)容