09.你以為我真的讓你手寫 Promise 嗎(1)?

通過前面幾節(jié)課的學(xué)習(xí),我們認(rèn)識到:想優(yōu)雅地進(jìn)行異步操作,必須要熟識一個極其重要的概念 —— Promise。它是取代傳統(tǒng)回調(diào),實現(xiàn)同步鏈?zhǔn)綄懛ǖ慕鉀Q方案;是理解 generator、async/await 的關(guān)鍵。但是 Promise 對于初學(xué)者來說,并不是很好理解,其中的概念紛雜,且抽象程度較高。

與此同時,在中高級前端開發(fā)面試當(dāng)中,對于 Promise 的考察也多種多樣,近幾年流行「讓開發(fā)者實現(xiàn)一個 Promise」。那么這一講,我就帶大家實現(xiàn)一個簡單的 Promise。注意:實現(xiàn)不是最終目的,在實現(xiàn)的過程中,我會配以關(guān)鍵結(jié)論和關(guān)于 Promise 的考察題目,希望大家可以融會貫通。

整個過程將分兩節(jié)課完成,本講的相關(guān)知識點如下:


圖片

從 Promise 化一個 API 談起

熟悉微信小程序開發(fā)的讀者應(yīng)該知道,我們使用 wx.request() 在微信小程序環(huán)境中發(fā)送一個網(wǎng)絡(luò)請求。參考官方文檔,具體用法如下:

wx.request({
 url: 'test.php', // 僅為示例,并非真實的接口地址
 data: {
   x: '',
   y: ''
 },
 header: {
   'content-type': 'application/json' // 默認(rèn)值
 },
 success(res) {
   console.log(res.data)
 }
})

配置化的 API 風(fēng)格和我們早期使用 jQuery 中 Ajax 方法的封裝類似。這樣的設(shè)計有一個小的問題,就是容易出現(xiàn)「回調(diào)地獄」問題。如果我們想先通過 ./userInfo 接口來獲取登錄用戶信息數(shù)據(jù),再從登錄用戶信息數(shù)據(jù)中,通過請求 ./${id}/friendList 接口來獲取登錄用戶所有好友列表,就需要:

wx.request({
 url: './userInfo',
 success(res) {
   const id = res.data.id
   wx.request({
     url: `./${id}/friendList`,
     success(res) {
       console.log(res)
     }
   })
 }
})

這只是嵌套了一層回調(diào)而已,還夠不成「地獄」場景,但是足以說明問題。

我們知道解決「回調(diào)地獄」問題的一個極佳方式就是 Promise,將微信小程序 wx.request() 方法進(jìn)行 Promise 化:

const wxRequest = (url, data = {}, method = 'GET') =>
 new Promise((resolve, reject) => {
   wx.request({
     url,
     data,
     method,
     header: {
       //通用化 header 設(shè)置
     },
     success: function (res) {
       const code = res.statusCode
       if (code !== 200) {
         reject({ error: 'request fail', code })
         return
       }
       resolve(res.data)
     },
     fail: function (res) {
       reject({ error: 'request fail'})
     },
   })
 })

Promise 基本概念不再過多介紹。這是一個典型的 Promise 化案例,當(dāng)然我們不僅可以對 wx.request() API 進(jìn)行 Promise 化,更應(yīng)該做的通用,能夠 Promise 化更多類似(通過 success 和 fail 表征狀態(tài))的接口:

const promisify = fn => args =>
 new Promise((resolve, reject) => {
   args.success = function(res) {
     return resolve(res)
   }
   args.fail = function(res) {
     return reject(res)
   }
 })

使用:

const wxRequest = promisify(wx.request)

通過上例,我們知道:

Promise 其實就是一個構(gòu)造函數(shù),我們使用這個構(gòu)造函數(shù)創(chuàng)建一個 Promise 實例。該構(gòu)造函數(shù)很簡單,它只有一個參數(shù),按照 Promise/A+ 規(guī)范的命名,把 Promise 構(gòu)造函數(shù)的參數(shù)叫做 executor,executor 類型為函數(shù)。這個函數(shù)又「自動」具有 resolve、reject 兩個方法作為參數(shù)。

請仔細(xì)體會上述結(jié)論,那么我們可以通過結(jié)論,開始實現(xiàn) Promise 的第一步:

function Promise(executor) {

}

好吧,初始起步是夠基本的了。如果讀者還不理解構(gòu)造函數(shù)的概念,我給大家推薦閱讀: 構(gòu)造函數(shù)與 new 命令,在理解的基礎(chǔ)上,讓我們繼續(xù)吧。

Promise 初見雛形

在上面的 wx.request() 介紹中,實現(xiàn)了 Promise 化,因此對于嵌套回調(diào)場景,可以:

wxRequest('./userInfo')
 .then(
   data => wxRequest(`./${data.id}/friendList`),
   error => {
     console.log(error)
   }
 )
 .then(
   data => {
     console.log(data)
   },
   error => {
     console.log(error)
   }
 )

通過觀察使用例子,我們來剖析 Promise 的實質(zhì):

結(jié)論 Promise 構(gòu)造函數(shù)返回一個 promise 對象實例,這個返回的 promise 對象具有一個 then 方法。then 方法中,調(diào)用者可以定義兩個參數(shù),分別是 onfulfilled 和 onrejected,它們都是函數(shù)類型。其中 onfulfilled 通過參數(shù),可以獲取 promise 對象 resolved 的值,onrejected 獲得 promise 對象 rejected 的值。通過這個值,我們來處理異步完成后的邏輯。

這些都是規(guī)范的基本內(nèi)容: Promise/A+

因此,繼續(xù)實現(xiàn)我們的 Promise:

function Promise(executor) {

}

Promise.prototype.then = function(onfulfilled, onrejected) {

}

繼續(xù)復(fù)習(xí) Promise 的知識,看例子來理解:

let promise1 = new Promise((resolve, reject) => {
 resolve('data')
})

promise1.then(data => {
 console.log(data)
})

let promise2 = new Promise((resolve, reject) => {
 reject('error')
})

promise2.then(data => {
 console.log(data)
}, error => {
 console.log(error)
})

結(jié)論 我們在使用 new 關(guān)鍵字調(diào)用 Promise 構(gòu)造函數(shù)時,在合適的時機(往往是異步結(jié)束時),調(diào)用 executor 的參數(shù) resolve 方法,并將 resolved 的值作為 resolve 函數(shù)參數(shù)執(zhí)行,這個值便可以后續(xù)在 then 方法第一個函數(shù)參數(shù)(onfulfilled)中拿到;同理,在出現(xiàn)錯誤時,調(diào)用 executor 的參數(shù) reject 方法,并將錯誤信息作為 reject 函數(shù)參數(shù)執(zhí)行,這個錯誤信息可以在后續(xù)的 then 方法第二個函數(shù)參數(shù)(onrejected)中拿到。

因此,我們在實現(xiàn) Promise 時,應(yīng)該有兩個值,分別儲存 resolved 的值,以及 rejected 的值(當(dāng)然,因為 Promise 狀態(tài)的唯一性,不可能同時出現(xiàn) resolved 的值和 rejected 的值,因此也可以用一個變量來存儲);同時也需要存在一個狀態(tài),這個狀態(tài)就是 promise 實例的狀態(tài)(pending,fulfilled,rejected);同時還要提供 resolve 方法以及 reject 方法,這兩個方法需要作為 executor 的參數(shù)提供給開發(fā)者使用:

function Promise(executor) {
 const self = this
 this.status = 'pending'
 this.value = null
 this.reason = null

 function resolve(value) {
   self.value = value
 }

 function reject(reason) {
   self.reason = reason
 }

 executor(resolve, reject)
}

Promise.prototype.then = function(onfulfilled = Function.prototype, onrejected = Function.prototype) {
 onfulfilled(this.value)

 onrejected(this.reason)
}

為了保證 onfulfilled、onrejected 能夠強健執(zhí)行,我們?yōu)槠湓O(shè)置了默認(rèn)值,其默認(rèn)值為一個函數(shù)元(Function.prototype)。

注意,因為 resolve 的最終調(diào)用是由開發(fā)者在不確定環(huán)境下(往往是在全局中)直接調(diào)用的。為了在 resolve 函數(shù)中能夠拿到 promise 實例的值,我們需要對 this 進(jìn)行保存,上述代碼中用 self 變量記錄 this,或者使用箭頭函數(shù):

function Promise(executor) {
 this.status = 'pending'
 this.value = null
 this.reason = null

 const resolve = value => {
   this.value = value
 }

 const reject = reason => {
   this.reason = reason
 }

 executor(resolve, reject)
}

Promise.prototype.then = function(onfulfilled = Function.prototype, onrejected = Function.prototype) {
 onfulfilled(this.value)

 onrejected(this.reason)
}

為什么 then 放在 Promise 構(gòu)造函數(shù)的原型上,而不是放在構(gòu)造函數(shù)內(nèi)部呢?

這涉及到原型、原型鏈的知識了,雖然不是本講的內(nèi)容,這里還是簡單地提一下:每個 promise 實例的 then 方法邏輯是一致的,在實例調(diào)用該方法時,可以通過原型(Promise.prototype)找到,而不需要每次實例化都新創(chuàng)建一個 then 方法,這樣節(jié)省內(nèi)存,顯然更合適。

Promise 實現(xiàn)狀態(tài)完善

我們先來看一到題目,判斷輸出:

let promise = new Promise((resolve, reject) => {
 resolve('data')
 reject('error')
})

promise.then(data => {
 console.log(data)
}, error => {
 console.log(error)
})

只會輸出:data,因為我們知道 promise 實例狀態(tài)只能從 pending 改變?yōu)?fulfilled,或者從 pending 改變?yōu)?rejected。狀態(tài)一旦變更完畢,就不可再次變化或者逆轉(zhuǎn)。也就是說:如果一旦變到 fulfilled,就不能再 rejected,一旦變到 rejected,就不能 fulfilled。

而我們的代碼實現(xiàn),顯然無法滿足這一特性。執(zhí)行上一段代碼時,將會輸出 data 以及 error。

因此,需要對狀態(tài)進(jìn)行判斷和完善:

function Promise(executor) {
 this.status = 'pending'
 this.value = null
 this.reason = null

 const resolve = value => {
   if (this.status === 'pending') {
     this.value = value
     this.status = 'fulfilled'
   }
 }

 const reject = reason => {
   if (this.status === 'pending') {
     this.reason = reason
     this.status = 'rejected'
   }
 }

 executor(resolve, reject)
}

Promise.prototype.then = function(onfulfilled, onrejected) {
 onfulfilled = typeof onfulfilled === 'function' ? onfulfilled : data => data
 onrejected = typeof onrejected === 'function' ? onrejected : error => {throw error}

 if (this.status === 'fulfilled') {
   onfulfilled(this.value)
 }
 if (this.status === 'rejected') {
   onrejected(this.reason)
 }
}

我們看,在 resolve 和 reject 方法中,我們加入判斷,只允許 promise 實例狀態(tài)從 pending 改變?yōu)?fulfilled,或者從 pending 改變?yōu)?rejected。

同時注意,這里我們對 Promise.prototype.then 參數(shù) onfulfilled 和 onrejected 進(jìn)行了判斷,當(dāng)實參不是一個函數(shù)類型時,賦予默認(rèn)函數(shù)值。這時候的默認(rèn)值不再是函數(shù)元 Function.prototype 了。為什么要這么更改?后面會有介紹。

這樣一來,我們的實現(xiàn)顯然更加接近真實了。剛才的例子也可以跑通了:

let promise = new Promise((resolve, reject) => {
 resolve('data')
 reject('error')
})

promise.then(data => {
 console.log(data)
}, error => {
 console.log(error)
})

但是不要高興得太早,promise 是解決異步問題的,我們的代碼全部都是同步執(zhí)行的,似乎還差了更重要的邏輯。

Promise 異步完善

到目前為止,實現(xiàn)還差了哪些內(nèi)容呢?別急,我們再從示例代碼分析:

let promise = new Promise((resolve, reject) => {
 setTimeout(() => {
   resolve('data')
 }, 2000)
})

promise.then(data => {
 console.log(data)
})

正常來講,上述代碼會在 2 秒之后輸出 data,但是我們實現(xiàn)的代碼,并沒有輸入任何信息。這是為什么呢?

原因很簡單,因為我們的實現(xiàn)邏輯全是同步的。在上面實例化一個 promise 的構(gòu)造函數(shù)時,我們是在 setTimeout 邏輯里才調(diào)用 resolve,也就是說,2 秒之后才會調(diào)用 resolve 方法,也才會去更改 promise 實例狀態(tài)。而結(jié)合我們的實現(xiàn),返回實現(xiàn)代碼,then 方法中的 onfulfilled 執(zhí)行是同步的,它在執(zhí)行時 this.status 仍然為 pending,并沒有做到「2 秒中之后再執(zhí)行 onfulfilled」。

那該怎么辦呢?我們似乎應(yīng)該在「合適」的時間才去調(diào)用 onfulfilled 方法,這個合適的時間就應(yīng)該是開發(fā)者調(diào)用 resolve 的時刻,那么我們先在狀態(tài)(status)為 pending 時,把開發(fā)者傳進(jìn)來的 onfulfilled 方法存起來,在 resolve 方法中再去執(zhí)行即可:

function Promise(executor) {
 this.status = 'pending'
 this.value = null
 this.reason = null
 this.onFulfilledFunc = Function.prototype
 this.onRejectedFunc = Function.prototype

 const resolve = value => {
   if (this.status === 'pending') {
     this.value = value
     this.status = 'fulfilled'

     this.onFulfilledFunc(this.value)
   }

 }

 const reject = reason => {
   if (this.status === 'pending') {
     this.reason = reason
     this.status = 'rejected'

     this.onRejectedFunc(this.reason)
   }
 }

 executor(resolve, reject)
}

Promise.prototype.then = function(onfulfilled, onrejected) {
 onfulfilled = typeof onfulfilled === 'function' ? onfulfilled : data => data
 onrejected = typeof onrejected === 'function' ? onrejected : error => {throw error}

 if (this.status === 'fulfilled') {
   onfulfilled(this.value)
 }
 if (this.status === 'rejected') {
   onrejected(this.reason)
 }
 if (this.status === 'pending') {
   this.onFulfilledFunc = onfulfilled
   this.onRejectedFunc = onrejected
 }
}

測試一下,發(fā)現(xiàn)現(xiàn)在我們的實現(xiàn)也可以支持異步了!

同時,我們知道 Promise 是異步執(zhí)行的:

let promise = new Promise((resolve, reject) => {
  resolve('data')
})

promise.then(data => {
 console.log(data)
})
console.log(1)

正常的話,這里會按照順序,輸出 1 再輸出 data。
而我們的實現(xiàn),卻沒有考慮這種情況,先輸出 data 再輸出 1。因此,需要將 resolve 和 reject 的執(zhí)行,放到任務(wù)隊列中。這里姑且先放到 setTimeout 里,保證異步執(zhí)行(這樣的做法并不嚴(yán)謹(jǐn),為了保證 Promise 屬于 microtasks,很多 Promise 的實現(xiàn)庫用了 MutationObserver 來模仿 nextTick)。

const resolve = value => {
 if (value instanceof Promise) {
   return value.then(resolve, reject)
 }
 setTimeout(() => {
   if (this.status === 'pending') {
     this.value = value
     this.status = 'fulfilled'

     this.onFulfilledFunc(this.value)
   }
 })
}

const reject = reason => {
 setTimeout(() => {
   if (this.status === 'pending') {
     this.reason = reason
     this.status = 'rejected'

     this.onRejectedFunc(this.reason)
   }
 })
}


executor(resolve, reject)

這樣一來,在執(zhí)行到 executor(resolve, reject) 時,也能保證在 nextTick 中才去執(zhí)行,不會阻塞同步任務(wù)。

同時我們在 resolve 方法中,加入了對 value 值是一個 Promise 實例的判斷。看一下到目前為止的實現(xiàn)代碼:

function Promise(executor) {
 this.status = 'pending'
 this.value = null
 this.reason = null
 this.onFulfilledFunc = Function.prototype
 this.onRejectedFunc = Function.prototype

 const resolve = value => {
   if (value instanceof Promise) {
     return value.then(resolve, reject)
   }
   setTimeout(() => {
     if (this.status === 'pending') {
       this.value = value
       this.status = 'fulfilled'

       this.onFulfilledFunc(this.value)
     }
   })
 }

 const reject = reason => {
   setTimeout(() => {
     if (this.status === 'pending') {
       this.reason = reason
       this.status = 'rejected'

       this.onRejectedFunc(this.reason)
     }
   })
 }

 executor(resolve, reject)
}

Promise.prototype.then = function(onfulfilled, onrejected) {
 onfulfilled = typeof onfulfilled === 'function' ? onfulfilled : data => data
 onrejected = typeof onrejected === 'function' ? onrejected : error => {throw error}

 if (this.status === 'fulfilled') {
   onfulfilled(this.value)
 }
 if (this.status === 'rejected') {
   onrejected(this.reason)
 }
 if (this.status === 'pending') {
   this.onFulfilledFunc = onfulfilled
   this.onRejectedFunc = onrejected
 }
}

這樣的實現(xiàn):

et promise = new Promise((resolve, reject) => {
  resolve('data')
})

promise.then(data => {
 console.log(data)
})
console.log(1)

也會按照順序,輸出 1 再輸出 data。

Promise 細(xì)節(jié)完善

到此為止,似乎我們的 Promise 實現(xiàn)越來越靠譜了,但是還有些細(xì)節(jié)需要完善。

比如當(dāng)我們在 promise 實例狀態(tài)變更之前,添加多個 then 方法:

let promise = new Promise((resolve, reject) => {
 setTimeout(() => {
   resolve('data')
 }, 2000)
})

promise.then(data => {
 console.log(`1: ${data}`)
})
promise.then(data => {
 console.log(`2: ${data}`)
})


應(yīng)該輸出:

1: data
2: data

而我們的實現(xiàn),只會輸出 2: data,這是因為第二個 then 方法中的 onFulfilledFunc 會覆蓋第一個 then 方法中的 onFulfilledFunc。

這個問題也好解決,只需要將所有 then 方法中的 onFulfilledFunc 儲存為一個數(shù)組 onFulfilledArray,在 resolve 時,依次執(zhí)行即可。對于 onRejectedFunc 同理,改動后的實現(xiàn)為:

function Promise(executor) {
 this.status = 'pending'
 this.value = null
 this.reason = null
 this.onFulfilledArray = []
 this.onRejectedArray = []

 const resolve = value => {
   if (value instanceof Promise) {
     return value.then(resolve, reject)
   }
   setTimeout(() => {
     if (this.status === 'pending') {
       this.value = value
       this.status = 'fulfilled'

       this.onFulfilledArray.forEach(func => {
         func(value)
       })
     }
   })
 }

 const reject = reason => {
   setTimeout(() => {
     if (this.status === 'pending') {
       this.reason = reason
       this.status = 'rejected'

       this.onRejectedArray.forEach(func => {
         func(reason)
       })
     }
   })
 }

 executor(resolve, reject)
}

Promise.prototype.then = function(onfulfilled, onrejected) {
 onfulfilled = typeof onfulfilled === 'function' ? onfulfilled : data => data
 onrejected = typeof onrejected === 'function' ? onrejected : error => {throw error}

 if (this.status === 'fulfilled') {
   onfulfilled(this.value)
 }
 if (this.status === 'rejected') {
   onrejected(this.reason)
 }
 if (this.status === 'pending') {
   this.onFulfilledArray.push(onfulfilled)
   this.onRejectedArray.push(onrejected)
 }
}

另外一個細(xì)節(jié),在構(gòu)造函數(shù)中如果出錯,將會自動觸發(fā) promise 實例狀態(tài)為 rejected,我們用 try...catch 塊對 executor 進(jìn)行包裹:

try {
 executor(resolve, reject)
} catch(e) {
 reject(e)
}

當(dāng)我們故意寫錯時:

let promise = new Promise((resolve, reject) => {
 setTout(() => {
   resolve('data')
 }, 2000)
})

promise.then(data => {
 console.log(data)
}, error => {
 console.log('got error from promise', error)
})

就可以對錯誤進(jìn)行處理,捕獲到:

got error from promise ReferenceError: setTimeouteout is not defined
   at :2:3
   at :33:7
   at o (web-46c6729d4d8cac92aed8.js:1)

總結(jié)

這一小節(jié),我們已經(jīng)初步實現(xiàn)了基本的 Promise,實現(xiàn)結(jié)果固然重要,但是在實現(xiàn)過程中,也加深了對 Promise 的理解,得出了一些重要結(jié)論:

  • Promise 狀態(tài)具有凝固性
  • Promise 錯誤處理
  • Promise 實例添加多個 then 處理

最后,附上到此為止的全部代碼:

function Promise(executor) {
 this.status = 'pending'
 this.value = null
 this.reason = null
 this.onFulfilledArray = []
 this.onRejectedArray = []

 const resolve = value => {
   if (value instanceof Promise) {
     return value.then(resolve, reject)
   }
   setTimeout(() => {
     if (this.status === 'pending') {
       this.value = value
       this.status = 'fulfilled'

       this.onFulfilledArray.forEach(func => {
         func(value)
       })
     }
   })
 }

 const reject = reason => {
   setTimeout(() => {
     if (this.status === 'pending') {
       this.reason = reason
       this.status = 'rejected'

       this.onRejectedArray.forEach(func => {
         func(reason)
       })
     }
   })
 }


 try {
   executor(resolve, reject)
 } catch(e) {
   reject(e)
 }
}

Promise.prototype.then = function(onfulfilled, onrejected) {
 onfulfilled = typeof onfulfilled === 'function' ? onfulfilled : data => data
 onrejected = typeof onrejected === 'function' ? onrejected : error => { throw error}

 if (this.status === 'fulfilled') {
   onfulfilled(this.value)
 }
 if (this.status === 'rejected') {
   onrejected(this.reason)
 }
 if (this.status === 'pending') {
   this.onFulfilledArray.push(onfulfilled)
   this.onRejectedArray.push(onrejected)
 }
}

下一講我們將會繼續(xù)實現(xiàn) Promise、處理 Promise 實例的返回問題,以及更多的 Promise 靜態(tài)方法。

閱讀原文

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

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