js紅寶書筆記十二 第十一章 期約與異步函數(shù)

本文繼續(xù)對(duì)JavaScript高級(jí)程序設(shè)計(jì)第四版 第十一章 期約與異步函數(shù) 進(jìn)行學(xué)習(xí)

建議先閱讀JS異步處理系列一 ES6 Promise,文章曾經(jīng)提到:

Promise也有一些缺點(diǎn)煤搜。首先,無法取消Promise,一旦新建它就會(huì)立即執(zhí)行,無法中途取消铅檩。其次花枫,如果不設(shè)置回調(diào)函數(shù),Promise內(nèi)部拋出的錯(cuò)誤诸尽,不會(huì)反應(yīng)到外部。第三印颤,當(dāng)處于pending狀態(tài)時(shí)您机,無法得知目前進(jìn)展到哪一個(gè)階段(剛剛開始還是即將完成)。

然后快速瀏覽紅寶書本章內(nèi)容年局,一直到11.2.5節(jié)际看,期約擴(kuò)展,針對(duì)上述缺點(diǎn)提出了解決方案某宪。

ES6 不支持取消期約和進(jìn)度通知仿村,一個(gè)主要原因就是這樣會(huì)導(dǎo)致期約連鎖和期約合成過度復(fù)雜化。比如在一個(gè)期約連鎖中兴喂,如果某個(gè)被其他期約依賴的期約被取消了或者發(fā)出了通知蔼囊,那么接下來應(yīng)該發(fā)生什么完全說不清楚。畢竟衣迷,如果取消了 Promise.all()中的一個(gè)期約畏鼓,或者期約連鎖中前面的期約發(fā)送了一個(gè)通知,那么接下來應(yīng)該怎么辦才比較合理呢壶谒?

一云矫、期約取消

我們經(jīng)常會(huì)遇到期約正在處理過程中,程序卻不再需要其結(jié)果的情形汗菜。這時(shí)候如果能夠取消期約就好了让禀。某些第三方庫,比如 Bluebird陨界,就提供了這個(gè)特性巡揍。實(shí)際上,TC39 委員會(huì)也曾準(zhǔn)備增加這個(gè)特性菌瘪,但相關(guān)提案最終被撤回了腮敌。結(jié)果,ES6 期約被認(rèn)為是“激進(jìn)的”:只要期約的邏輯開始執(zhí)行,就沒有辦法阻止它執(zhí)行到完成糜工。

實(shí)際上弊添,可以在現(xiàn)有實(shí)現(xiàn)基礎(chǔ)上提供一種臨時(shí)性的封裝,以實(shí)現(xiàn)取消期約的功能捌木。這可以用到 KevinSmith 提到的“取消令牌”(cancel token)油坝。生成的令牌實(shí)例提供了一個(gè)接口,利用這個(gè)接口可以取消期約钮莲;同時(shí)也提供了一個(gè)期約的實(shí)例免钻,可以用來觸發(fā)取消后的操作并求值取消狀態(tài)彼水。

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>菜鳥教程(runoob.com)</title>
</head>
<button id="start">Start</button>
<button id="cancel">Cancel</button>
<script>

class CancelToken {
 constructor(cancelFn) {
 this.promise = new Promise((resolve, reject) => {
 cancelFn(() => {
 setTimeout(console.log, 0, "delay cancelled");
 resolve();
 });
 });
 }
}

const startButton = document.querySelector('#start');
const cancelButton = document.querySelector('#cancel');

function cancellableDelayedResolve(delay) {
 setTimeout(console.log, 0, "set delay");

 return new Promise((resolve, reject) => {

 const id = setTimeout((() => {
     setTimeout(console.log, 0, "delayed resolve");
     resolve();
 }), delay); 

 const cancelToken = new CancelToken((cancelCallback) =>
 cancelButton.addEventListener("click", cancelCallback));
 cancelToken.promise.then(() => clearTimeout(id));

 });
}

startButton.addEventListener("click", () => cancellableDelayedResolve(3000));
</script> 
</html>

每次單擊“Start”按鈕都會(huì)開始計(jì)時(shí)崔拥,并實(shí)例化一個(gè)新的 CancelToken 的實(shí)例。此時(shí)凤覆,“Cancel”按鈕一旦被點(diǎn)擊链瓦,就會(huì)觸發(fā)令牌實(shí)例中的期約解決。而解決之后盯桦,單擊“Start”按鈕設(shè)置的超時(shí)也會(huì)被取消慈俯。

CancelToken類包裝了一個(gè)期約,把解決方法暴露給了 cancelFn 參數(shù)拥峦。這樣贴膘,外部代碼就可以向構(gòu)造函數(shù)中傳入一個(gè)函數(shù),從而控制什么情況下可以取消期約略号。這里期約是令牌類的公共成員刑峡,因此可以給它添加處理程序以取消期約。

二玄柠、期約進(jìn)度通知

執(zhí)行中的期約可能會(huì)有不少離散的“階段”突梦,在最終解決之前必須依次經(jīng)過。某些情況下羽利,監(jiān)控期約的執(zhí)行進(jìn)度會(huì)很有用宫患。ECMAScript 6 期約并不支持進(jìn)度追蹤,但是可以通過擴(kuò)展來實(shí)現(xiàn)这弧。

一種實(shí)現(xiàn)方式是擴(kuò)展 Promise 類娃闲,為它添加 notify()方法,如下所示:

class TrackablePromise extends Promise {
 constructor(executor) {
 const notifyHandlers = [];
 super((resolve, reject) => {
 return executor(resolve, reject, (status) => {
 notifyHandlers.map((handler) => handler(status));
 });
 });
 this.notifyHandlers = notifyHandlers;
 }
 notify(notifyHandler) {
 this.notifyHandlers.push(notifyHandler);
 return this;
 }
}

這樣匾浪,TrackablePromise 就可以在執(zhí)行函數(shù)中使用 notify()函數(shù)了皇帮。可以像下面這樣使用這個(gè)函數(shù)來實(shí)例化一個(gè)期約:

let p = new TrackablePromise((resolve, reject, notify) => {
 function countdown(x) {
 if (x > 0) {
 notify(`${20 * x}% remaining`);
 setTimeout(() => countdown(x - 1), 1000);
 } else {
 resolve();
 }
 }
 countdown(5);
});

這個(gè)期約會(huì)連續(xù)5次遞歸地設(shè)置1000毫秒的超時(shí)户矢。每個(gè)超時(shí)回調(diào)都會(huì)調(diào)用notify()并傳入狀態(tài)值玲献。假設(shè)通知處理程序簡(jiǎn)單地這樣寫:

let p = new TrackablePromise((resolve, reject, notify) => {
 function countdown(x) {
 if (x > 0) {
 notify(`${20 * x}% remaining`);
 setTimeout(() => countdown(x - 1), 1000);
 } else {
 resolve();
 }
 }
 countdown(5);
});
p.notify((x) => setTimeout(console.log, 0, 'progress:', x));
p.then(() => setTimeout(console.log, 0, 'completed'));
// (約 1 秒后)80% remaining
// (約 2 秒后)60% remaining
// (約 3 秒后)40% remaining
// (約 4 秒后)20% remaining
// (約 5 秒后)completed

notify()函數(shù)會(huì)返回期約,所以可以連綴調(diào)用,連續(xù)添加處理程序捌年。多個(gè)處理程序會(huì)針對(duì)收到的每條消息分別執(zhí)行一遍瓢娜,如下所示:

p.notify((x) => setTimeout(console.log, 0, 'a:', x))
.notify((x) => setTimeout(console.log, 0, 'b:', x));
p.then(() => setTimeout(console.log, 0, 'completed'));
// (約 1 秒后) a: 80% remaining
// (約 1 秒后) b: 80% remaining
// (約 2 秒后) a: 60% remaining
// (約 2 秒后) b: 60% remaining
// (約 3 秒后) a: 40% remaining
// (約 3 秒后) b: 40% remaining
// (約 4 秒后) a: 20% remaining
// (約 4 秒后) b: 20% remaining
// (約 5 秒后) completed

總體來看,這還是一個(gè)比較粗糙的實(shí)現(xiàn)礼预,但應(yīng)該可以演示出如何使用通知報(bào)告進(jìn)度了眠砾。

三、停止和恢復(fù)執(zhí)行

推薦先閱讀JS異步處理系列三 async await
快速瀏覽紅寶書11.3.1節(jié)后托酸,來到了11.3.2節(jié)褒颈。

使用 await 關(guān)鍵字之后的區(qū)別其實(shí)比看上去的還要微妙一些。比如励堡,下面的例子中按順序調(diào)用了 3個(gè)函數(shù)谷丸,但它們的輸出結(jié)果順序是相反的:

async function foo() {
 console.log(await Promise.resolve('foo'));
}
async function bar() {
 console.log(await 'bar');
}
async function baz() {
 console.log('baz');
}
foo();
bar();
baz();
// baz
// bar
// foo

async/await 中真正起作用的是 await。async 關(guān)鍵字应结,無論從哪方面來看刨疼,都不過是一個(gè)標(biāo)識(shí)符。畢竟鹅龄,異步函數(shù)如果不包含 await 關(guān)鍵字揩慕,其執(zhí)行基本上跟普通函數(shù)沒有什么區(qū)別:

async function foo() {
 console.log(2);
}
console.log(1);
foo();
console.log(3); 
// 1
// 2
// 3 

要完全理解 await 關(guān)鍵字,必須知道它并非只是等待一個(gè)值可用那么簡(jiǎn)單扮休。JavaScript 運(yùn)行時(shí)在碰到 await 關(guān)鍵字時(shí)迎卤,會(huì)記錄在哪里暫停執(zhí)行。等到 await 右邊的值可用了玷坠,JavaScript 運(yùn)行時(shí)會(huì)向消息隊(duì)列中推送一個(gè)任務(wù)蜗搔,這個(gè)任務(wù)會(huì)恢復(fù)異步函數(shù)的執(zhí)行。

因此侨糟,即使 await 后面跟著一個(gè)立即可用的值碍扔,函數(shù)的其余部分也會(huì)被異步求值。下面的例子演示了這一點(diǎn):

async function foo() {
 console.log(2);
 await null;
 console.log(4);
}
console.log(1);
foo();
console.log(3);
// 1
// 2
// 3
// 4 

控制臺(tái)中輸出結(jié)果的順序很好地解釋了運(yùn)行時(shí)的工作過程:

  • (1) 打印 1秕重;
  • (2) 調(diào)用異步函數(shù) foo()不同;
  • (3)(在 foo()中)打印 2;
  • (4)(在 foo()中)await 關(guān)鍵字暫停執(zhí)行溶耘,為立即可用的值 null 向消息隊(duì)列中添加一個(gè)任務(wù)二拐;
  • (5) foo()退出;
  • (6) 打印 3凳兵;
  • (7) 同步線程的代碼執(zhí)行完畢百新;
  • (8) JavaScript 運(yùn)行時(shí)從消息隊(duì)列中取出任務(wù),恢復(fù)異步函數(shù)執(zhí)行庐扫;
  • (9)(在 foo()中)恢復(fù)執(zhí)行饭望,await 取得 null 值(這里并沒有使用)仗哨;
  • (10)(在 foo()中)打印 4;
  • (11) foo()返回铅辞。
四厌漂、異步函數(shù)策略

因?yàn)楹?jiǎn)單實(shí)用,所以異步函數(shù)很快成為 JavaScript 項(xiàng)目使用最廣泛的特性之一斟珊。不過苇倡,在使用異步函數(shù)時(shí),還是有些問題要注意囤踩。

1. 實(shí)現(xiàn) sleep()

很多人在剛開始學(xué)習(xí) JavaScript 時(shí)旨椒,想找到一個(gè)類似 Java 中 Thread.sleep()之類的函數(shù),好在程序中加入非阻塞的暫停堵漱。以前综慎,這個(gè)需求基本上都通過 setTimeout()利用 JavaScript 運(yùn)行時(shí)的行為來實(shí)現(xiàn)的。有了異步函數(shù)之后怔锌,就不一樣了寥粹。一個(gè)簡(jiǎn)單的箭頭函數(shù)就可以實(shí)現(xiàn) sleep():

async function sleep(delay) {
 return new Promise((resolve) => setTimeout(resolve, delay));
}
async function foo() {
 const t0 = Date.now();
 await sleep(1500); // 暫停約 1500 毫秒
 console.log(Date.now() - t0);
}
foo();
// 1502
2. 利用平行執(zhí)行

如果使用 await 時(shí)不留心变过,則很可能錯(cuò)過平行加速的機(jī)會(huì)埃元。來看下面的例子,其中順序等待了 5個(gè)隨機(jī)的超時(shí):

async function randomDelay(id) {
 // 延遲 0~1000 毫秒
 const delay = Math.random() * 1000;
 return new Promise((resolve) => setTimeout(() => {
 console.log(`${id} finished`);
 resolve();
 }, delay));
}
async function foo() {
 const t0 = Date.now();
 await randomDelay(0);
 await randomDelay(1);
 await randomDelay(2);
 await randomDelay(3);
 await randomDelay(4);
 console.log(`${Date.now() - t0}ms elapsed`);
}
foo();
// 0 finished
// 1 finished
// 2 finished
// 3 finished
// 4 finished
// 877ms elapsed 

用一個(gè) for 循環(huán)重寫媚狰,就是:

async function randomDelay(id) {
 // 延遲 0~1000 毫秒
 const delay = Math.random() * 1000;
 return new Promise((resolve) => setTimeout(() => {
 console.log(`${id} finished`);
 resolve();
 }, delay));
}
async function foo() {
 const t0 = Date.now();
 for (let i = 0; i < 5; ++i) {
 await randomDelay(i);
 }
 console.log(`${Date.now() - t0}ms elapsed`);
}
foo();
// 0 finished
// 1 finished
// 2 finished
// 3 finished
// 4 finished
// 877ms elapsed 

就算這些期約之間沒有依賴岛杀,異步函數(shù)也會(huì)依次暫停,等待每個(gè)超時(shí)完成崭孤。這樣可以保證執(zhí)行順序类嗤,但總執(zhí)行時(shí)間會(huì)變長(zhǎng)。如果順序不是必需保證的辨宠,那么可以先一次性初始化所有期約遗锣,然后再分別等待它們的結(jié)果。比如:

async function randomDelay(id) {
 // 延遲 0~1000 毫秒
 const delay = Math.random() * 1000;
 return new Promise((resolve) => setTimeout(() => {
 setTimeout(console.log, 0, `${id} finished`);
 resolve();
 }, delay));
}
async function foo() {
 const t0 = Date.now();
 const p0 = randomDelay(0);
 const p1 = randomDelay(1);
 const p2 = randomDelay(2);
 const p3 = randomDelay(3);
 const p4 = randomDelay(4);
 await p0;
 await p1;
 await p2;
 await p3;
 await p4;
 setTimeout(console.log, 0, `${Date.now() - t0}ms elapsed`);
}
foo();
// 1 finished
// 4 finished
// 3 finished
// 0 finished
// 2 finished
// 877ms elapsed 

用數(shù)組和 for 循環(huán)再包裝一下就是:

async function randomDelay(id) {
 // 延遲 0~1000 毫秒
 const delay = Math.random() * 1000;
 return new Promise((resolve) => setTimeout(() => {
 console.log(`${id} finished`);
 resolve();
 }, delay));
}
async function foo() {
 const t0 = Date.now();
 const promises = Array(5).fill(null).map((_, i) => randomDelay(i));
 for (const p of promises) {
 await p;
 }
 console.log(`${Date.now() - t0}ms elapsed`);
}
foo();
// 4 finished
// 2 finished
// 1 finished
// 0 finished
// 3 finished
// 877ms elapsed

注意嗤形,雖然期約沒有按照順序執(zhí)行精偿,但 await 按順序收到了每個(gè)期約的值:

async function randomDelay(id) {
 // 延遲 0~1000 毫秒
 const delay = Math.random() * 1000;
 return new Promise((resolve) => setTimeout(() => {
 console.log(`${id} finished`);
 resolve(id);
 }, delay));
}
async function foo() {
 const t0 = Date.now();
 const promises = Array(5).fill(null).map((_, i) => randomDelay(i));
 for (const p of promises) {
 console.log(`awaited ${await p}`);
 }
 console.log(`${Date.now() - t0}ms elapsed`);
}
foo(); 
// 1 finished
// 2 finished
// 4 finished
// 3 finished
// 0 finished
// awaited 0
// awaited 1
// awaited 2
// awaited 3
// awaited 4
// 645ms elapsed 
3.串行執(zhí)行期約

在 11.2 節(jié),我們討論過如何串行執(zhí)行期約并把值傳給后續(xù)的期約赋兵。使用 async/await笔咽,期約連鎖會(huì)變得很簡(jiǎn)單:

function addTwo(x) {return x + 2;}
function addThree(x) {return x + 3;}
function addFive(x) {return x + 5;}
async function addTen(x) {
 for (const fn of [addTwo, addThree, addFive]) {
 x = await fn(x);
 }
 return x;
}
addTen(9).then(console.log); // 19

這里,await 直接傳遞了每個(gè)函數(shù)的返回值霹期,結(jié)果通過迭代產(chǎn)生叶组。當(dāng)然,這個(gè)例子并沒有使用期約历造,如果要使用期約甩十,則可以把所有函數(shù)都改成異步函數(shù)船庇。這樣它們就都返回期約了:

async function addTwo(x) {return x + 2;}
async function addThree(x) {return x + 3;}
async function addFive(x) {return x + 5;}
async function addTen(x) {
 for (const fn of [addTwo, addThree, addFive]) {
 x = await fn(x);
 }
 return x;
}
addTen(9).then(console.log); // 19 
4.棧追蹤與內(nèi)存管理

期約與異步函數(shù)的功能有相當(dāng)程度的重疊,但它們?cè)趦?nèi)存中的表示則差別很大侣监∫缡看看下面的例子,它展示了拒絕期約的棧追蹤信息:

function fooPromiseExecutor(resolve, reject) {
 setTimeout(reject, 1000, 'bar');
}
function foo() {
 new Promise(fooPromiseExecutor);
} 
foo();
// Uncaught (in promise) bar
// setTimeout
// setTimeout (async)
// fooPromiseExecutor
// foo

根據(jù)對(duì)期約的不同理解程度达吞,以上棧追蹤信息可能會(huì)讓某些讀者不解张弛。棧追蹤信息應(yīng)該相當(dāng)直接地表現(xiàn) JavaScript 引擎當(dāng)前棧內(nèi)存中函數(shù)調(diào)用之間的嵌套關(guān)系。在超時(shí)處理程序執(zhí)行時(shí)和拒絕期約時(shí)酪劫,我們看到的錯(cuò)誤信息包含嵌套函數(shù)的標(biāo)識(shí)符吞鸭,那是被調(diào)用以創(chuàng)建最初期約實(shí)例的函數(shù)「苍悖可是刻剥,我們知道這些函數(shù)已經(jīng)返回了,因此棧追蹤信息中不應(yīng)該看到它們滩字。

答案很簡(jiǎn)單造虏,這是因?yàn)?JavaScript 引擎會(huì)在創(chuàng)建期約時(shí)盡可能保留完整的調(diào)用棧。在拋出錯(cuò)誤時(shí)麦箍,調(diào)用椑炫海可以由運(yùn)行時(shí)的錯(cuò)誤處理邏輯獲取,因而就會(huì)出現(xiàn)在棧追蹤信息中挟裂。當(dāng)然享钞,這意味著棧追蹤信息會(huì)占用內(nèi)存,從而帶來一些計(jì)算和存儲(chǔ)成本诀蓉。

如果在前面的例子中使用的是異步函數(shù)栗竖,那又會(huì)怎樣呢?比如:

function fooPromiseExecutor(resolve, reject) {
 setTimeout(reject, 1000, 'bar');
}
async function foo() {
 await new Promise(fooPromiseExecutor);
}
foo();
// Uncaught (in promise) bar
// foo
// async function (async)
// foo

這樣一改渠啤,棧追蹤信息就準(zhǔn)確地反映了當(dāng)前的調(diào)用棧狐肢。fooPromiseExecutor()已經(jīng)返回,所以它不在錯(cuò)誤信息中沥曹。但 foo()此時(shí)被掛起了份名,并沒有退出。JavaScript 運(yùn)行時(shí)可以簡(jiǎn)單地在嵌套函數(shù)中存儲(chǔ)指向包含函數(shù)的指針架专,就跟對(duì)待同步函數(shù)調(diào)用棧一樣同窘。這個(gè)指針實(shí)際上存儲(chǔ)在內(nèi)存中,可用于在出錯(cuò)時(shí)生成棧追蹤信息部脚。這樣就不會(huì)像之前的例子那樣帶來額外的消耗想邦,因此在重視性能的應(yīng)用中是可以優(yōu)先考慮的。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末委刘,一起剝皮案震驚了整個(gè)濱河市丧没,隨后出現(xiàn)的幾起案子鹰椒,更是在濱河造成了極大的恐慌,老刑警劉巖呕童,帶你破解...
    沈念sama閱讀 222,807評(píng)論 6 518
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件漆际,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡夺饲,警方通過查閱死者的電腦和手機(jī)奸汇,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,284評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來往声,“玉大人擂找,你說我怎么就攤上這事『葡” “怎么了贯涎?”我有些...
    開封第一講書人閱讀 169,589評(píng)論 0 363
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)慢洋。 經(jīng)常有香客問我塘雳,道長(zhǎng),這世上最難降的妖魔是什么普筹? 我笑而不...
    開封第一講書人閱讀 60,188評(píng)論 1 300
  • 正文 為了忘掉前任败明,我火速辦了婚禮,結(jié)果婚禮上斑芜,老公的妹妹穿的比我還像新娘肩刃。我一直安慰自己,他們只是感情好杏头,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,185評(píng)論 6 398
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著沸呐,像睡著了一般醇王。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上崭添,一...
    開封第一講書人閱讀 52,785評(píng)論 1 314
  • 那天寓娩,我揣著相機(jī)與錄音,去河邊找鬼呼渣。 笑死棘伴,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的屁置。 我是一名探鬼主播焊夸,決...
    沈念sama閱讀 41,220評(píng)論 3 423
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼蓝角!你這毒婦竟也來了阱穗?” 一聲冷哼從身側(cè)響起饭冬,我...
    開封第一講書人閱讀 40,167評(píng)論 0 277
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎揪阶,沒想到半個(gè)月后昌抠,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,698評(píng)論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡鲁僚,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,767評(píng)論 3 343
  • 正文 我和宋清朗相戀三年炊苫,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片冰沙。...
    茶點(diǎn)故事閱讀 40,912評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡劝评,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出倦淀,到底是詐尸還是另有隱情蒋畜,我是刑警寧澤,帶...
    沈念sama閱讀 36,572評(píng)論 5 351
  • 正文 年R本政府宣布撞叽,位于F島的核電站姻成,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏愿棋。R本人自食惡果不足惜科展,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,254評(píng)論 3 336
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望糠雨。 院中可真熱鬧才睹,春花似錦、人聲如沸甘邀。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,746評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽松邪。三九已至坞琴,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間逗抑,已是汗流浹背剧辐。 一陣腳步聲響...
    開封第一講書人閱讀 33,859評(píng)論 1 274
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留邮府,地道東北人荧关。 一個(gè)月前我還...
    沈念sama閱讀 49,359評(píng)論 3 379
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像褂傀,于是被迫代替她去往敵國(guó)和親忍啤。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,922評(píng)論 2 361

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