你不懂JS:ES6與未來(lái) 第四章:異步流程控制

官方中文版原文鏈接

感謝社區(qū)中各位的大力支持,譯者再次奉上一點(diǎn)點(diǎn)福利:阿里云產(chǎn)品券,享受所有官網(wǎng)優(yōu)惠,并抽取幸運(yùn)大獎(jiǎng):點(diǎn)擊這里領(lǐng)取

如果你寫(xiě)過(guò)任何數(shù)量相當(dāng)?shù)腏avaScript,這就不是什么秘密:異步編程是一種必須的技能。管理異步的主要機(jī)制曾經(jīng)是函數(shù)回調(diào)。

然而,ES6增加了一種新特性:Promise,來(lái)幫助你解決僅使用回調(diào)來(lái)管理異步的重大缺陷。另外,我們可以重溫generator(前一章中提到的)來(lái)看看一種將兩者組合的模式,它是JavaScript中異步流程控制編程向前邁出的重要一步。

Promises

讓我們辨明一些誤解:Promise不是回調(diào)的替代品。Promise提供了一種可信的中介機(jī)制 —— 也就是,在你的調(diào)用代碼和將要執(zhí)行任務(wù)的異步代碼之間 —— 來(lái)管理回調(diào)。

另一種考慮Promise的方式是作為一種事件監(jiān)聽(tīng)器,你可以在它上面注冊(cè)監(jiān)聽(tīng)一個(gè)通知你任務(wù)何時(shí)完成的事件。它是一個(gè)僅被觸發(fā)一次的事件,但不管怎樣可以被看作是一個(gè)事件。

Promise可以被鏈接在一起,它們可以是一系列順序的、異步完成的步驟。與all(..)方法(用經(jīng)典的術(shù)語(yǔ)將,叫“門(mén)”)和race(..)方法(用經(jīng)典的術(shù)語(yǔ)將,叫“閂”)這樣的高級(jí)抽象一起,promise鏈可以提供一種異步流程控制的機(jī)制。

還有另外一種概念化Promise的方式是,將它看作一個(gè) 未來(lái)值,一個(gè)與時(shí)間無(wú)關(guān)的值的容器。無(wú)論底層的值是否是最終值,這種容器都可以被同樣地推理。觀測(cè)一個(gè)Promise的解析會(huì)在這個(gè)值準(zhǔn)備好的時(shí)候?qū)⑺槿〕鰜?lái)。換言之,一個(gè)Promise被認(rèn)為是一個(gè)同步函數(shù)返回值的異步版本。

一個(gè)Promise只可能擁有兩種解析結(jié)果:完成或拒絕,并帶有一個(gè)可選的信號(hào)值。如果一個(gè)Promise被完成,這個(gè)最終值稱(chēng)為一個(gè)完成值。如果它被拒絕,這個(gè)最終值稱(chēng)為理由(也就是“拒絕的理由”)。Promise只可能被解析(完成或拒絕)一次。任何其他的完成或拒絕的嘗試都會(huì)被簡(jiǎn)單地忽略,一旦一個(gè)Promise被解析,它就成為一個(gè)不可被改變的值(immutable)。

顯然,有幾種不同的方式可以來(lái)考慮一個(gè)Promise是什么。沒(méi)有一個(gè)角度就它自身來(lái)說(shuō)是完全充分的,但是每一個(gè)角度都提供了整體的一個(gè)方面。這其中的要點(diǎn)是,它們?yōu)閮H使用回調(diào)的異步提供了一個(gè)重大的改進(jìn),也就是它們提供了順序、可預(yù)測(cè)性、以及可信性。

創(chuàng)建與使用 Promises

要構(gòu)建一個(gè)promise實(shí)例,可以使用Promise(..)構(gòu)造器:

var p = new Promise( function pr(resolve,reject){
    // ..
} );

Promise(..)構(gòu)造器接收一個(gè)單獨(dú)的函數(shù)(pr(..)),它被立即調(diào)用并以參數(shù)值的形式收到兩個(gè)控制函數(shù),通常被命名為resolve(..)reject(..)。它們被這樣使用:

  • 如果你調(diào)用reject(..),promise就會(huì)被拒絕,而且如果有任何值被傳入reject(..),它就會(huì)被設(shè)置為拒絕的理由。
  • 如果你不使用參數(shù)值,或任何非promise值調(diào)用resolve(..),promise就會(huì)被完成。
  • 如果你調(diào)用resolve(..)并傳入另一個(gè)promise,這個(gè)promise就會(huì)簡(jiǎn)單地采用 —— 要么立即要么最終地 —— 這個(gè)被傳入的promise的狀態(tài)(不是完成就是拒絕)。

這里是你通常如何使用一個(gè)promise來(lái)重構(gòu)一個(gè)依賴(lài)于回調(diào)的函數(shù)調(diào)用。假定你始于使用一個(gè)ajax(..)工具,它期預(yù)期要調(diào)用一個(gè)錯(cuò)誤優(yōu)先風(fēng)格的回調(diào):

function ajax(url,cb) {
    // 發(fā)起請(qǐng)求,最終調(diào)用 `cb(..)`
}

// ..

ajax( "http://some.url.1", function handler(err,contents){
    if (err) {
        // 處理ajax錯(cuò)誤
    }
    else {
        // 處理成功的`contents`
    }
} );

你可以將它轉(zhuǎn)換為:

function ajax(url) {
    return new Promise( function pr(resolve,reject){
        // 發(fā)起請(qǐng)求,最終不是調(diào)用 `resolve(..)` 就是調(diào)用 `reject(..)`
    } );
}

// ..

ajax( "http://some.url.1" )
.then(
    function fulfilled(contents){
        // 處理成功的 `contents`
    },
    function rejected(reason){
        // 處理ajax的錯(cuò)誤reason
    }
);

Promise擁有一個(gè)方法then(..),它接收一個(gè)或兩個(gè)回調(diào)函數(shù)。第一個(gè)函數(shù)(如果存在的話(huà))被看作是promise被成功地完成時(shí)要調(diào)用的處理器。第二個(gè)函數(shù)(如果存在的話(huà))被看作是promise被明確拒絕時(shí),或者任何錯(cuò)誤/異常在解析的過(guò)程中被捕捉到時(shí)要調(diào)用的處理器。

如果這兩個(gè)參數(shù)值之一被省略或者不是一個(gè)合法的函數(shù) —— 通常你會(huì)用null來(lái)代替 —— 那么一個(gè)占位用的默認(rèn)等價(jià)物就會(huì)被使用。默認(rèn)的成功回調(diào)將傳遞它的完成值,而默認(rèn)的錯(cuò)誤回調(diào)將傳播它的拒絕理由。

調(diào)用then(null,handleRejection)的縮寫(xiě)是catch(handleRejection)

then(..)catch(..)兩者都自動(dòng)地構(gòu)建并返回另一個(gè)promise實(shí)例,它被鏈接在原本的promise上,接收原本的promise的解析結(jié)果 —— (實(shí)際被調(diào)用的)完成或拒絕處理器返回的任何值。考慮如下代碼:

ajax( "http://some.url.1" )
.then(
    function fulfilled(contents){
        return contents.toUpperCase();
    },
    function rejected(reason){
        return "DEFAULT VALUE";
    }
)
.then( function fulfilled(data){
    // 處理來(lái)自于原本的promise的處理器中的數(shù)據(jù)
} );

在這個(gè)代碼段中,我們要么從fulfilled(..)返回一個(gè)立即值,要么從rejected(..)返回一個(gè)立即值,然后在下一個(gè)事件周期中這個(gè)立即值被第二個(gè)then(..)fulfilled(..)接收。如果我們返回一個(gè)新的promise,那么這個(gè)新promise就會(huì)作為解析結(jié)果被納入與采用:

ajax( "http://some.url.1" )
.then(
    function fulfilled(contents){
        return ajax(
            "http://some.url.2?v=" + contents
        );
    },
    function rejected(reason){
        return ajax(
            "http://backup.url.3?err=" + reason
        );
    }
)
.then( function fulfilled(contents){
    // `contents` 來(lái)自于任意一個(gè)后續(xù)的 `ajax(..)` 調(diào)用
} );

要注意的是,在第一個(gè)fulfilled(..)中的一個(gè)異常(或者promise拒絕)將 不會(huì) 導(dǎo)致第一個(gè)rejected(..)被調(diào)用,因?yàn)檫@個(gè)處理僅會(huì)應(yīng)答第一個(gè)原始的promise的解析。取代它的是,第二個(gè)then(..)調(diào)用所針對(duì)的第二個(gè)promise,將會(huì)收到這個(gè)拒絕。

在上面的代碼段中,我們沒(méi)有監(jiān)聽(tīng)這個(gè)拒絕,這意味著它會(huì)為了未來(lái)的觀察而被靜靜地保持下來(lái)。如果你永遠(yuǎn)不通過(guò)調(diào)用then(..)catch(..)來(lái)觀察它,那么它將會(huì)成為未處理的。有些瀏覽器的開(kāi)發(fā)者控制臺(tái)可能會(huì)探測(cè)到這些未處理的拒絕并報(bào)告它們,但是這不是有可靠保證的;你應(yīng)當(dāng)總是觀察promise拒絕。

注意: 這只是Promise理論和行為的簡(jiǎn)要概覽。要進(jìn)行更加深入的探索,參見(jiàn)本系列的 異步與性能 的第三章。

Thenables

Promise是Promise(..)構(gòu)造器的純粹實(shí)例。然而,還存在稱(chēng)為 thenable 的類(lèi)promise對(duì)象,它通常可以與Promise機(jī)制協(xié)作。

任何帶有then(..)函數(shù)的對(duì)象(或函數(shù))都被認(rèn)為是一個(gè)thenable。任何Promise機(jī)制可以接受與采用一個(gè)純粹的promise的狀態(tài)的地方,都可以處理一個(gè)thenable。

Thenable基本上是一個(gè)一般化的標(biāo)簽,標(biāo)識(shí)著任何由除了Promise(..)構(gòu)造器之外的其他系統(tǒng)創(chuàng)建的類(lèi)promise值。從這個(gè)角度上講,一個(gè)thenable沒(méi)有一個(gè)純粹的Promise那么可信。例如,考慮這個(gè)行為異常的thenable:

var th = {
    then: function thener( fulfilled ) {
        // 永遠(yuǎn)會(huì)每100ms調(diào)用一次`fulfilled(..)`
        setInterval( fulfilled, 100 );
    }
};

如果你收到這個(gè)thenable并使用th.then(..)將它鏈接,你可能會(huì)驚訝地發(fā)現(xiàn)你的完成處理器被反復(fù)地調(diào)用,而普通的Promise本應(yīng)該僅僅被解析一次。

一般來(lái)說(shuō),如果你從某些其他系統(tǒng)收到一個(gè)聲稱(chēng)是promise或thenable的東西,你不應(yīng)當(dāng)盲目地相信它。在下一節(jié)中,我們將會(huì)看到一個(gè)ES6 Promise的工具,它可以幫助解決信任的問(wèn)題。

但是為了進(jìn)一步理解這個(gè)問(wèn)題的危險(xiǎn),讓我們考慮一下,在 任何 一段代碼中的 任何 對(duì)象,只要曾經(jīng)被定義為擁有一個(gè)稱(chēng)為then(..)的方法就都潛在地會(huì)被誤認(rèn)為是一個(gè)thenable —— 當(dāng)然,如果和Promise一起使用的話(huà) —— 無(wú)論這個(gè)東西是否有意與Promise風(fēng)格的異步編碼有一絲關(guān)聯(lián)。

在ES6之前,對(duì)于稱(chēng)為then(..)的方法從來(lái)沒(méi)有任何特別的保留措施,正如你能想象的那樣,在Promise出現(xiàn)在雷達(dá)屏幕上之前就至少有那么幾種情況,它已經(jīng)被選擇為方法的名稱(chēng)了。最有可能用錯(cuò)thenable的情況就是使用then(..)的異步庫(kù)不是嚴(yán)格兼容Promise的 —— 在市面上有好幾種。

這份重?fù)?dān)將由你來(lái)肩負(fù):防止那些將被誤認(rèn)為一個(gè)thenable的值被直接用于Promise機(jī)制。

Promise API

PromiseAPI還為處理Promise提供了一些靜態(tài)方法。

Promise.resolve(..)創(chuàng)建一個(gè)被解析為傳入的值的promise。讓我們將它的工作方式與更手動(dòng)的方法比較一下:

var p1 = Promise.resolve( 42 );

var p2 = new Promise( function pr(resolve){
    resolve( 42 );
} );

p1p2將擁有完全相同的行為。使用一個(gè)promise進(jìn)行解析也一樣:

var theP = ajax( .. );

var p1 = Promise.resolve( theP );

var p2 = new Promise( function pr(resolve){
    resolve( theP );
} );

提示: Promise.resolve(..)就是前一節(jié)提出的thenable信任問(wèn)題的解決方案。任何你還不確定是一個(gè)可信promise的值 —— 它甚至可能是一個(gè)立即值 —— 都可以通過(guò)傳入Promise.resolve(..)來(lái)進(jìn)行規(guī)范化。如果這個(gè)值已經(jīng)是一個(gè)可識(shí)別的promise或thenable,它的狀態(tài)/解析結(jié)果將簡(jiǎn)單地被采用,將錯(cuò)誤行為與你隔絕開(kāi)。如果相反它是一個(gè)立即值,那么它將會(huì)被“包裝”進(jìn)一個(gè)純粹的promise,以此將它的行為規(guī)范化為異步的。

Promise.reject(..)創(chuàng)建一個(gè)立即被拒絕的promise,與它的Promise(..)構(gòu)造器對(duì)等品一樣:

var p1 = Promise.reject( "Oops" );

var p2 = new Promise( function pr(resolve,reject){
    reject( "Oops" );
} );

雖然resolve(..)Promise.resolve(..)可以接收一個(gè)promise并采用它的狀態(tài)/解析結(jié)果,但是reject(..)Promise.reject(..)不會(huì)區(qū)分它們收到什么樣的值。所以,如果你使用一個(gè)promise或thenable進(jìn)行拒絕,這個(gè)promise/thenable本身將會(huì)被設(shè)置為拒絕的理由,而不是它底層的值。

Promise.all([ .. ])接收一個(gè)或多個(gè)值(例如,立即值,promise,thenable)的數(shù)組。它返回一個(gè)promise,這個(gè)promise會(huì)在所有的值完成時(shí)完成,或者在這些值中第一個(gè)被拒絕的值出現(xiàn)時(shí)被立即拒絕。

使用這些值/promises:

var p1 = Promise.resolve( 42 );
var p2 = new Promise( function pr(resolve){
    setTimeout( function(){
        resolve( 43 );
    }, 100 );
} );
var v3 = 44;
var p4 = new Promise( function pr(resolve,reject){
    setTimeout( function(){
        reject( "Oops" );
    }, 10 );
} );

讓我們考慮一下使用這些值的組合,Promise.all([ .. ])如何工作:

Promise.all( [p1,p2,v3] )
.then( function fulfilled(vals){
    console.log( vals );            // [42,43,44]
} );

Promise.all( [p1,p2,v3,p4] )
.then(
    function fulfilled(vals){
        // 永遠(yuǎn)不會(huì)跑到這里
    },
    function rejected(reason){
        console.log( reason );      // Oops
    }
);

Promise.all([ .. ])等待所有的值完成(或第一個(gè)拒絕),而Promise.race([ .. ])僅會(huì)等待第一個(gè)完成或拒絕。考慮如下代碼:

// 注意:為了避免時(shí)間的問(wèn)題誤導(dǎo)你,
// 重建所有的測(cè)試值!

Promise.race( [p2,p1,v3] )
.then( function fulfilled(val){
    console.log( val );             // 42
} );

Promise.race( [p2,p4] )
.then(
    function fulfilled(val){
        // 永遠(yuǎn)不會(huì)跑到這里
    },
    function rejected(reason){
        console.log( reason );      // Oops
    }
);

警告: 雖然 Promise.all([])將會(huì)立即完成(沒(méi)有任何值),但是 Promise.race([])將會(huì)被永遠(yuǎn)掛起。這是一個(gè)奇怪的不一致,我建議你應(yīng)當(dāng)永遠(yuǎn)不要使用空數(shù)組調(diào)用這些方法。

Generators + Promises

將一系列promise在一個(gè)鏈條中表達(dá)來(lái)代表你程序的異步流程控制是 可能 的。考慮如如下代碼:

step1()
.then(
    step2,
    step1Failed
)
.then(
    function step3(msg) {
        return Promise.all( [
            step3a( msg ),
            step3b( msg ),
            step3c( msg )
        ] )
    }
)
.then(step4);

但是對(duì)于表達(dá)異步流程控制來(lái)說(shuō)有更好的選項(xiàng),而且在代碼風(fēng)格上可能比長(zhǎng)長(zhǎng)的promise鏈更理想。我們可以使用在第三章中學(xué)到的generator來(lái)表達(dá)我們的異步流程控制。

要識(shí)別一個(gè)重要的模式:一個(gè)generator可以yield出一個(gè)promise,然后這個(gè)promise可以使用它的完成值來(lái)推進(jìn)generator。

考慮前一個(gè)代碼段,使用generator來(lái)表達(dá):

function *main() {

    try {
        var ret = yield step1();
    }
    catch (err) {
        ret = yield step1Failed( err );
    }

    ret = yield step2( ret );

    // step 3
    ret = yield Promise.all( [
        step3a( ret ),
        step3b( ret ),
        step3c( ret )
    ] );

    yield step4( ret );
}

從表面上看,這個(gè)代碼段要比前一個(gè)promise鏈等價(jià)物要更繁冗。但是它提供了更加吸引人的 —— 而且重要的是,更加容易理解和閱讀的 —— 看起來(lái)同步的代碼風(fēng)格(“return”值的=賦值操作,等等),對(duì)于try..catch錯(cuò)誤處理可以跨越那些隱藏的異步邊界使用來(lái)說(shuō)就更是這樣。

為什么我們要與generator一起使用Promise?不用Promise進(jìn)行異步generator編碼當(dāng)然是可能的。

Promise是一個(gè)可信的系統(tǒng),它將普通的回調(diào)和thunk中發(fā)生的控制倒轉(zhuǎn)(參見(jiàn)本系列的 異步與性能)反轉(zhuǎn)回來(lái)。所以組合Promise的可信性與generator中代碼的同步性有效地解決了回調(diào)的主要缺陷。另外,像Promise.all([ .. ])這樣的工具是一個(gè)非常美好、干凈的方式 —— 在一個(gè)generator的一個(gè)yield步驟中表達(dá)并發(fā)。

那么這種魔法是如何工作的?我們需要一個(gè)可以運(yùn)行我們generator的 運(yùn)行器(runner),接收一個(gè)被yield出來(lái)的promise并連接它,讓它要么使用成功的完成推進(jìn)generator,要么使用拒絕的理由向generator拋出異常。

許多具備異步能力的工具/庫(kù)都有這樣的“運(yùn)行器”;例如,Q.spawn(..)和我的asynquence中的runner(..)插件。這里有一個(gè)獨(dú)立的運(yùn)行器來(lái)展示這種處理如何工作:

function run(gen) {
    var args = [].slice.call( arguments, 1), it;

    it = gen.apply( this, args );

    return Promise.resolve()
        .then( function handleNext(value){
            var next = it.next( value );

            return (function handleResult(next){
                if (next.done) {
                    return next.value;
                }
                else {
                    return Promise.resolve( next.value )
                        .then(
                            handleNext,
                            function handleErr(err) {
                                return Promise.resolve(
                                    it.throw( err )
                                )
                                .then( handleResult );
                            }
                        );
                }
            })( next );
        } );
}

注意: 這個(gè)工具的更豐富注釋的版本,參見(jiàn)本系列的 異步與性能。另外,由各種異步庫(kù)提供的這種運(yùn)行工具通常要比我們?cè)谶@里展示的東西更強(qiáng)大。例如,asynquence的runner(..)可以處理被yield的promise、序列、thunk、以及(非promise的)間接值,給你終極的靈活性。

于是現(xiàn)在運(yùn)行早先代碼段中的*main()就像這樣容易:

run( main )
.then(
    function fulfilled(){
        // `*main()` 成功地完成了
    },
    function rejected(reason){
        // 噢,什么東西搞錯(cuò)了
    }
);

實(shí)質(zhì)上,在你程序中的任何擁有多于兩個(gè)異步步驟的流程控制邏輯的地方,你就可以 而且應(yīng)當(dāng) 使用一個(gè)由運(yùn)行工具驅(qū)動(dòng)的promise-yielding generator來(lái)以一種同步的風(fēng)格表達(dá)流程控制。這樣做將產(chǎn)生更易于理解和維護(hù)的代碼。

這種“讓出一個(gè)promise推進(jìn)generator”的模式將會(huì)如此常見(jiàn)和如此強(qiáng)大,以至于ES6之后的下一個(gè)版本的JavaScript幾乎可以確定將會(huì)引入一中新的函數(shù)類(lèi)型,它無(wú)需運(yùn)行工具就可以自動(dòng)地執(zhí)行。我們將在第八章中講解async function(正如它們期望被稱(chēng)呼的那樣)。

復(fù)習(xí)

隨著JavaScript在它被廣泛采用過(guò)程中的日益成熟與成長(zhǎng),異步編程越發(fā)地成為關(guān)注的中心。對(duì)于這些異步任務(wù)來(lái)說(shuō)回調(diào)并不完全夠用,而且在更精巧的需求面前全面崩塌了。

可喜的是,ES6增加了Promise來(lái)解決回調(diào)的主要缺陷之一:在可預(yù)測(cè)的行為上缺乏可信性。Promise代表一個(gè)潛在異步任務(wù)的未來(lái)完成值,跨越同步和異步的邊界將行為進(jìn)行了規(guī)范化。

但是,Promise與generator的組合才完全揭示了這樣做的好處:將我們的異步流程控制代碼重新安排,將難看的回調(diào)漿糊(也叫“地獄”)弱化并抽象出去。

目前,我們可以在各種異步庫(kù)的運(yùn)行器的幫助下管理這些交互,但是JavaScript最終將會(huì)使用一種專(zhuān)門(mén)的獨(dú)立語(yǔ)法來(lái)支持這種交互模式!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,578評(píng)論 6 544
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,701評(píng)論 3 429
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事。” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 178,691評(píng)論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 63,974評(píng)論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 72,694評(píng)論 6 413
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 56,026評(píng)論 1 329
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,015評(píng)論 3 450
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 43,193評(píng)論 0 290
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,719評(píng)論 1 336
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 41,442評(píng)論 3 360
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,668評(píng)論 1 374
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,151評(píng)論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,846評(píng)論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 35,255評(píng)論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 36,592評(píng)論 1 295
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 52,394評(píng)論 3 400
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,635評(píng)論 2 380

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