Promise的三兄弟:all(), race()以及allSettled()

摘要: 玩轉(zhuǎn)Promise。

Fundebug經(jīng)授權(quán)轉(zhuǎn)載诲宇,版權(quán)歸原作者所有。

從ES6 開始惶翻,我們大都使用的是 Promise.all()Promise.race()姑蓝,Promise.allSettled() 提案已經(jīng)到第4階段,因此將會(huì)成為ECMAScript 2020的一部分吕粗。

1.概述

Promise.all<T>(promises: Iterable<Promise<T>>): Promise<Array<T>>

  • Promise.all(iterable) 方法返回一個(gè) Promise 實(shí)例它掂,此實(shí)例在 iterable 參數(shù)內(nèi)所有的 promise 都“完成(resolved)”或參數(shù)中不包含 promise 時(shí)回調(diào)完成(resolve);如果參數(shù)中 promise 有一個(gè)失敗(rejected)虐秋,此實(shí)例回調(diào)失旈偶搿(reject),失敗原因的是第一個(gè)失敗 promise 的結(jié)果

Promise.race<T>(promises: Iterable<Promise<T>>): Promise<T>

  • Promise.race(iterable) 方法返回一個(gè) promise客给,一旦迭代器中的某個(gè)promise解決或拒絕用押,返回的 promise就會(huì)解決或拒絕。

Promise.allSettled<T>(promises: Iterable<Promise<T>>): Promise<Array<SettlementObject<T>>>

  • Promise.allSettled()方法返回一個(gè)promise靶剑,該promise在所有給定的promise已被解析或被拒絕后解析蜻拨,并且每個(gè)對(duì)象都描述每個(gè)promise的結(jié)果。

2. 回顧: Promise 狀態(tài)

給定一個(gè)返回Promise的異步操作桩引,以下這些是Promise的可能狀態(tài):

  • pending: 初始狀態(tài)缎讼,既不是成功,也不是失敗狀態(tài)坑匠。
  • fulfilled: 意味著操作成功完成血崭。
  • rejected: 意味著操作失敗。
  • Settled: Promise要么被完成厘灼,要么被拒絕夹纫。Promise一旦達(dá)成,它的狀態(tài)就不再改變设凹。
image

3.什么是組合

又稱部分-整體模式舰讹,將對(duì)象整合成樹形結(jié)構(gòu)以表示“部分整體”的層次結(jié)構(gòu)。組合模式使得用戶對(duì)單個(gè)對(duì)象和組合對(duì)象的使用具有一致性闪朱,它基于兩種函數(shù):

  • 基元函數(shù)(簡(jiǎn)短:基元)創(chuàng)建原子塊月匣。
  • 組合函數(shù)(簡(jiǎn)稱:組合)將原子和/或復(fù)合件組合在一起以形成復(fù)合件。

對(duì)于 JS 的 Promises 來說

  • 基元函數(shù)包括:Promise.resolve()奋姿、Promise.reject()
  • 組合函數(shù):Promise.all(), Promise.race(), Promise.allSettled()

4. Promise.all()

Promise.all()的類型簽名:

  • Promise.all<T>(promises: Iterable<Promise<T>>): Promise<Array<T>>

返回情況:

完成(Fulfillment):
如果傳入的可迭代對(duì)象為空没酣,Promise.all 會(huì)同步地返回一個(gè)已完成(resolved)狀態(tài)的promise停团。
如果所有傳入的 promise 都變?yōu)橥瓿蔂顟B(tài),或者傳入的可迭代對(duì)象內(nèi)沒有 promisePromise.all 返回的 promise 異步地變?yōu)橥瓿伞?br> 在任何情況下唱遭,Promise.all 返回的 promise 的完成狀態(tài)的結(jié)果都是一個(gè)數(shù)組罗心,它包含所有的傳入迭代參數(shù)對(duì)象的值(也包括非 promise 值)电抚。

失敗/拒絕(Rejection):
如果傳入的 promise 中有一個(gè)失敶垦堋(rejected),Promise.all 異步地將失敗的那個(gè)結(jié)果給失敗狀態(tài)的回調(diào)函數(shù)再榄,而不管其它 promise 是否完成狡刘。

來個(gè)例子:

    const promises = [
      Promise.resolve('a'),
      Promise.resolve('b'),
      Promise.resolve('c'),
    ];
    Promise.all(promises)
      .then((arr) => assert.deepEqual(
        arr, ['a', 'b', 'c']
      ));

如果其中的一個(gè) promise 被拒絕,那么又是什么情況:

    const promises = [
      Promise.resolve('a'),
      Promise.resolve('b'),
      Promise.reject('ERROR'),
    ];
    Promise.all(promises)
      .catch((err) => assert.equal(
        err, 'ERROR'
      ));

下圖說明Promise.all()是如何工作的

image

4.1 異步 .map() 與 Promise.all()

數(shù)組轉(zhuǎn)換方法困鸥,如.map()嗅蔬、.filter()等剑按,用于同步計(jì)算。例如

    function timesTwoSync(x) {
      return 2 * x;
    }
    const arr = [1, 2, 3];
    const result = arr.map(timesTwoSync);
    assert.deepEqual(result, [2, 4, 6]);

如果.map()的回調(diào)是基于Promise的函數(shù)會(huì)發(fā)生什么澜术? 使用這種方式 .map()返回的的結(jié)果是一個(gè)Promises數(shù)組艺蝴。

Promises數(shù)組不是普通代碼可以使用的數(shù)據(jù),但我們可以通過Promise.all()來解決這個(gè)問題:它將Promises數(shù)組轉(zhuǎn)換為Promise鸟废,并使用一組普通值數(shù)組來實(shí)現(xiàn)猜敢。

    function timesTwoAsync(x) {
      return new Promise(resolve => resolve(x * 2));
    }
    const arr = [1, 2, 3];
    const promiseArr = arr.map(timesTwoAsync);
    Promise.all(promiseArr)
      .then(result => {
        assert.deepEqual(result, [2, 4, 6]);
      });

更實(shí)際工作上關(guān)于 .map()示例

接下來,咱們使用.map()Promise.all()Web下載文件盒延。 首先缩擂,咱們需要以下幫助函數(shù):

    function downloadText(url) {
      return fetch(url)
        .then((response) => { // (A)
          if (!response.ok) { // (B)
            throw new Error(response.statusText);
          }
          return response.text(); // (C)
        });
    }

downloadText()使用基于Promise的fetch API 以字符串流的方式下載文件:

  • 首先,它異步檢索響應(yīng)(第A行)添寺。
  • response.ok(B行)檢查是否存在“找不到文件”等錯(cuò)誤胯盯。
  • 如果沒有錯(cuò)誤,使用.text()(第C行)以字符串的形式取回文件的內(nèi)容计露。

在下面的示例中博脑,咱們 下載了兩個(gè)文件

    const urls = [
      'http://example.com/first.txt',
      'http://example.com/second.txt',
    ];

    const promises = urls.map(
      url => downloadText(url));
    
    Promise.all(promises)
      .then(
        (arr) => assert.deepEqual(
          arr, ['First!', 'Second!']
        ));

Promise.all()的一個(gè)簡(jiǎn)版實(shí)現(xiàn)

    function all(iterable) {
      return new Promise((resolve, reject) => {
        let index = 0;
        for (const promise of iterable) {
          // Capture the current value of `index`
          const currentIndex = index;
          promise.then(
            (value) => {
              if (anErrorOccurred) return;
              result[currentIndex] = value;
              elementCount++;
              if (elementCount === result.length) {
                resolve(result);
              }
            },
            (err) => {
              if (anErrorOccurred) return;
              anErrorOccurred = true;
              reject(err);
            });
          index++;
        }
        if (index === 0) {
          resolve([]);
          return;
        }
        let elementCount = 0;
        let anErrorOccurred = false;
        const result = new Array(index);
      });
    }

5. Promise.race()

Promise.race()方法的定義:

Promise.race<T>(promises: Iterable<Promise<T>>): Promise<T>

Promise.race(iterable) 方法返回一個(gè) promise,一旦迭代器中的某個(gè)promise解決或拒絕薄坏,返回的 promise就會(huì)解決或拒絕趋厉。來幾個(gè)例子寨闹,瞧瞧:

const promises = [
  new Promise((resolve, reject) =>
    setTimeout(() => resolve('result'), 100)), // (A)
  new Promise((resolve, reject) =>
    setTimeout(() => reject('ERROR'), 200)), // (B)
];
Promise.race(promises)
  .then((result) => assert.equal( // (C)
    result, 'result'));

在第 A 行胶坠,Promise 是完成狀態(tài) ,所以 第 C 行會(huì)執(zhí)行(盡管第 B 行被拒絕)繁堡。

如果 Promise 被拒絕首先執(zhí)行沈善,在來看看情況是嘛樣的:

    const promises = [
      new Promise((resolve, reject) =>
        setTimeout(() => resolve('result'), 200)),
      new Promise((resolve, reject) =>
        setTimeout(() => reject('ERROR'), 100)),
    ];
    Promise.race(promises)
      .then(
        (result) => assert.fail(),
        (err) => assert.equal(
          err, 'ERROR'));

注意,由于 Promse 先被拒絕椭蹄,所以 Promise.race() 返回的是一個(gè)被拒絕的 Promise

這意味著Promise.race([])的結(jié)果永遠(yuǎn)不會(huì)完成闻牡。

下圖演示了Promise.race()的工作原理:

image

Promise.race() 在 Promise 超時(shí)下的情況

在本節(jié)中,我們將使用Promise.race()來處理超時(shí)的 Promise绳矩。 以下輔助函數(shù):

    function resolveAfter(ms, value=undefined) {
      return new Promise((resolve, reject) => {
        setTimeout(() => resolve(value), ms);
      });
    }

resolveAfter() 主要做的是在指定的時(shí)間內(nèi)罩润,返回一個(gè)狀態(tài)為 resolvePromise,值為為傳入的 value

調(diào)用上面方法:

    function timeout(timeoutInMs, promise) {
      return Promise.race([
        promise,
        resolveAfter(timeoutInMs,
          Promise.reject(new Error('Operation timed out'))),
      ]);
    }

timeout() 返回一個(gè)Promise翼馆,該 Promise 的狀態(tài)取決于傳入 promise 狀態(tài) 割以。

其中 timeout 函數(shù)中的 resolveAfter(timeoutInMs, Promise.reject(new Error('Operation timed out')) ,通過 resolveAfter 定義可知应媚,該結(jié)果返回的是一個(gè)被拒絕狀態(tài)的 Promise严沥。

再來看看timeout(timeoutInMs, promise)的運(yùn)行情況。如果傳入promise在指定的時(shí)間之前狀態(tài)為完成時(shí)中姜,timeout 返回結(jié)果就是一個(gè)完成狀態(tài)的 Promise,可以通過.then的第一個(gè)回調(diào)參數(shù)處理返回的結(jié)果消玄。

    timeout(200, resolveAfter(100, 'Result!'))
      .then(result => assert.equal(result, 'Result!'));

相反,如果是在指定的時(shí)間之后完成,剛 timeout 返回結(jié)果就是一個(gè)拒絕狀態(tài)的 Promise,從而觸發(fā)catch方法指定的回調(diào)函數(shù)翩瓜。

    timeout(100, resolveAfter(2000, 'Result!'))
      .catch(err => assert.deepEqual(err, new Error('Operation timed out')));

重要的是要了解“Promise 超時(shí)”的真正含義:

  1. 如果傳入入Promise 較到的得到解決受扳,其結(jié)果就會(huì)給返回的 Promise
  2. 如果沒有足夠快得到解決兔跌,輸出的 Promise 的狀態(tài)為拒絕辞色。

也就是說,超時(shí)只會(huì)阻止傳入的Promise浮定,影響輸出 Promise(因?yàn)镻romise只能解決一次)相满, 但它并沒有阻止傳入Promise的異步操作。

5.2 Promise.race() 的一個(gè)簡(jiǎn)版實(shí)現(xiàn)

以下是 Promise.race()的一個(gè)簡(jiǎn)化實(shí)現(xiàn)(它不執(zhí)行安全檢查)

    function race(iterable) {
      return new Promise((resolve, reject) => {
        for (const promise of iterable) {
          promise.then(
            (value) => {
              if (settlementOccurred) return;
              settlementOccurred = true;
              resolve(value);
            },
            (err) => {
              if (settlementOccurred) return;
              settlementOccurred = true;
              reject(err);
            });
        }
        let settlementOccurred = false;
      });
    }

6.Promise.allSettled()

“Promise.allSettled”這一特性是由Jason Williams桦卒,Robert PamelyMathias Bynens提出立美。

promise.allsettle()方法的定義:

  • Promise.allSettled<T>(promises: Iterable<Promise<T>>)
    : Promise<Array<SettlementObject<T>>>

它返回一個(gè)ArrayPromise,其元素具有以下類型特征:

    type SettlementObject<T> = FulfillmentObject<T> | RejectionObject;
    
    interface FulfillmentObject<T> {
      status: 'fulfilled';
      value: T;
    }
    
    interface RejectionObject {
      status: 'rejected';
      reason: unknown;
    }

Promise.allSettled()方法返回一個(gè)promise方灾,該promise在所有給定的promise已被解析或被拒絕后解析建蹄,并且每個(gè)對(duì)象都描述每個(gè)promise的結(jié)果。

舉例說明, 比如各位用戶在頁面上面同時(shí)填了3個(gè)獨(dú)立的表單, 這三個(gè)表單分三個(gè)接口提交到后端, 三個(gè)接口獨(dú)立, 沒有順序依賴, 這個(gè)時(shí)候我們需要等到請(qǐng)求全部完成后給與用戶提示表單提交的情況

在多個(gè)promise同時(shí)進(jìn)行時(shí)咱們很快會(huì)想到使用Promise.all來進(jìn)行包裝, 但是由于Promise.all的短路特性, 三個(gè)提交中若前面任意一個(gè)提交失敗, 則后面的表單也不會(huì)進(jìn)行提交了, 這就與咱們需求不符合.

Promise.allSettledPromise.all類似, 其參數(shù)接受一個(gè)Promise的數(shù)組, 返回一個(gè)新的Promise, 唯一的不同在于, 其不會(huì)進(jìn)行短路, 也就是說當(dāng)Promise全部處理完成后我們可以拿到每個(gè)Promise的狀態(tài), 而不管其是否處理成功.

下圖說明promise.allsettle()是如何工作的

image

6.1 Promise.allSettled() 例子

這是Promise.allSettled() 使用方式快速演示示例

    Promise.allSettled([
      Promise.resolve('a'),
      Promise.reject('b'),
    ])
    .then(arr => assert.deepEqual(arr, [
      { status: 'fulfilled', value:  'a' },
      { status: 'rejected',  reason: 'b' },
    ]));

6.2 Promise.allSettled() 較復(fù)雜點(diǎn)的例子

這個(gè)示例類似于.map()Promise.all()示例(我們從其中借用了downloadText()函數(shù)):我們下載多個(gè)文本文件裕偿,這些文件的url存儲(chǔ)在一個(gè)數(shù)組中洞慎。但是,這一次嘿棘,咱們不希望在出現(xiàn)錯(cuò)誤時(shí)停止劲腿,而是希望繼續(xù)執(zhí)行。Promise.allSettled()允許咱們這樣做:

    const urls = [
      'http://example.com/exists.txt',
      'http://example.com/missing.txt',
    ];
    
    const result = Promise.allSettled(
      urls.map(u => downloadText(u)));
    result.then(
      arr => assert.deepEqual(
        arr,
        [
          {
            status: 'fulfilled',
            value: 'Hello!',
          },
          {
            status: 'rejected',
            reason: new Error('Not Found'),
          },
        ]
    ));

6.3 Promise.allSettled() 的簡(jiǎn)化實(shí)現(xiàn)

這是promise.allsettle()的簡(jiǎn)化實(shí)現(xiàn)(不執(zhí)行安全檢查)

    function allSettled(iterable) {
      return new Promise((resolve, reject) => {
        function addElementToResult(i, elem) {
          result[i] = elem;
          elementCount++;
          if (elementCount === result.length) {
            resolve(result);
          }
        }
    
        let index = 0;
        for (const promise of iterable) {
          // Capture the current value of `index`
          const currentIndex = index;
          promise.then(
            (value) => addElementToResult(
              currentIndex, {
                status: 'fulfilled',
                value
              }),
            (reason) => addElementToResult(
              currentIndex, {
                status: 'rejected',
                reason
              }));
          index++;
        }
        if (index === 0) {
          resolve([]);
          return;
        }
        let elementCount = 0;
        const result = new Array(index);
      });
    }

7. 短路特性

Promise.all()romise.race() 都具有 短路特性

  • Promise.all(): 如果參數(shù)中 promise 有一個(gè)失斈衩睢(rejected)焦人,此實(shí)例回調(diào)失敗(reject)

Promise.race():如果參數(shù)中某個(gè)promise解決或拒絕重父,返回的 promise就會(huì)解決或拒絕花椭。

8.并發(fā)性和 Promise.all()

8.1 順序執(zhí)行與并發(fā)執(zhí)行

考慮下面的代碼:

    asyncFunc1()
      .then(result1 => {
        assert.equal(result1, 'one');
        return asyncFunc2();
      })
      .then(result2 => {
        assert.equal(result2, 'two');
      });

使用.then()順序執(zhí)行基于Promise的函數(shù):只有在 asyncFunc1()的結(jié)果被解決后才會(huì)執(zhí)行asyncFunc2()

Promise.all() 是并發(fā)執(zhí)行的

    Promise.all([asyncFunc1(), asyncFunc2()])
      .then(arr => {
        assert.deepEqual(arr, ['one', 'two']);
      });

9.2 并發(fā)技巧:關(guān)注操作何時(shí)開始

確定并發(fā)異步代碼的技巧:關(guān)注異步操作何時(shí)啟動(dòng)房午,而不是如何處理它們的Promises矿辽。

例如,下面的每個(gè)函數(shù)都同時(shí)執(zhí)行asyncFunc1()asyncFunc2()郭厌,因?yàn)樗鼈儙缀跬瑫r(shí)啟動(dòng)袋倔。

    function concurrentAll() {
      return Promise.all([asyncFunc1(), asyncFunc2()]);
    }
    
    function concurrentThen() {
      const p1 = asyncFunc1();
      const p2 = asyncFunc2();
      return p1.then(r1 => p2.then(r2 => [r1, r2]));
    }

另一方面,以下兩個(gè)函數(shù)依次執(zhí)行asyncFunc1()asyncFunc2(): asyncFunc2()僅在asyncFunc1()的解決之后才調(diào)用沪曙。

    function sequentialThen() {
      return asyncFunc1()
        .then(r1 => asyncFunc2()
          .then(r2 => [r1, r2]));
    }
    
    function sequentialAll() {
      const p1 = asyncFunc1();
      const p2 = p1.then(() => asyncFunc2());
      return Promise.all([p1, p2]);
    }

9.3 Promise.all() 與 Fork-Join 分治編程

Promise.all() 與并發(fā)模式“fork join”松散相關(guān)奕污。重溫一下咱們前面的一個(gè)例子:

    Promise.all([
        // (A) fork
        downloadText('http://example.com/first.txt'),
        downloadText('http://example.com/second.txt'),
      ])
      // (B) join
      .then(
        (arr) => assert.deepEqual(
          arr, ['First!', 'Second!']
        ));
  • Fork:在A行中,分割兩個(gè)異步任務(wù)并同時(shí)執(zhí)行它們液走。
  • Join:在B行中碳默,對(duì)每個(gè)小任務(wù)得到的結(jié)果進(jìn)行匯總贾陷。

代碼部署后可能存在的BUG沒法實(shí)時(shí)知道,事后為了解決這些BUG嘱根,花了大量的時(shí)間進(jìn)行l(wèi)og 調(diào)試髓废,這邊順便給大家推薦一個(gè)好用的BUG監(jiān)控工具 Fundebug

原文:https://2ality.com/2019/08/promise-combinators.html

關(guān)于Fundebug

Fundebug專注于JavaScript该抒、微信小程序慌洪、微信小游戲、支付寶小程序凑保、React Native冈爹、Node.js和Java線上應(yīng)用實(shí)時(shí)BUG監(jiān)控。 自從2016年雙十一正式上線欧引,F(xiàn)undebug累計(jì)處理了20億+錯(cuò)誤事件频伤,付費(fèi)客戶有陽光保險(xiǎn)、核桃編程芝此、荔枝FM憋肖、掌門1對(duì)1、微脈婚苹、青團(tuán)社等眾多品牌企業(yè)岸更。歡迎大家免費(fèi)試用

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末膊升,一起剝皮案震驚了整個(gè)濱河市怎炊,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌用僧,老刑警劉巖结胀,帶你破解...
    沈念sama閱讀 218,284評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件赞咙,死亡現(xiàn)場(chǎng)離奇詭異责循,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)攀操,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,115評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門院仿,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人速和,你說我怎么就攤上這事歹垫。” “怎么了颠放?”我有些...
    開封第一講書人閱讀 164,614評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵排惨,是天一觀的道長。 經(jīng)常有香客問我碰凶,道長暮芭,這世上最難降的妖魔是什么鹿驼? 我笑而不...
    開封第一講書人閱讀 58,671評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮辕宏,結(jié)果婚禮上畜晰,老公的妹妹穿的比我還像新娘。我一直安慰自己瑞筐,他們只是感情好凄鼻,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,699評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著聚假,像睡著了一般块蚌。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上膘格,一...
    開封第一講書人閱讀 51,562評(píng)論 1 305
  • 那天匈子,我揣著相機(jī)與錄音,去河邊找鬼闯袒。 笑死虎敦,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的政敢。 我是一名探鬼主播其徙,決...
    沈念sama閱讀 40,309評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼喷户,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了佳吞?” 一聲冷哼從身側(cè)響起鹊汛,我...
    開封第一講書人閱讀 39,223評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后扫皱,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,668評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡粹污,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,859評(píng)論 3 336
  • 正文 我和宋清朗相戀三年段多,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片厕怜。...
    茶點(diǎn)故事閱讀 39,981評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡衩匣,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出粥航,到底是詐尸還是另有隱情,我是刑警寧澤生百,帶...
    沈念sama閱讀 35,705評(píng)論 5 347
  • 正文 年R本政府宣布递雀,位于F島的核電站,受9級(jí)特大地震影響蚀浆,放射性物質(zhì)發(fā)生泄漏缀程。R本人自食惡果不足惜搜吧,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,310評(píng)論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望杨凑。 院中可真熱鬧滤奈,春花似錦、人聲如沸撩满。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,904評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽伺帘。三九已至昭躺,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間伪嫁,已是汗流浹背领炫。 一陣腳步聲響...
    開封第一講書人閱讀 33,023評(píng)論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留张咳,地道東北人帝洪。 一個(gè)月前我還...
    沈念sama閱讀 48,146評(píng)論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像脚猾,于是被迫代替她去往敵國和親碟狞。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,933評(píng)論 2 355

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