【轉(zhuǎn)向JavaScript系列】深入理解Promise

本文緣起前段時間一朋友換工作時,筆試題中要求手寫一個Promise偎行。在工作中雖已大量使用Promise猖败,其原理卻沒有深入探究過,換做自己懂扼,當場也很難手寫一個完善的Promise實現(xiàn)。近期補了一些課右蒲,以本文來徹底的理解Promise實現(xiàn)原理阀湿。

1.Promise是什么

Promise是抽象異步處理對象以及對其進行各種操作的組件,可以將復(fù)雜的異步處理輕松的進行模式化瑰妄。
使用Promise進行異步處理的一個例子

function getUserId() {
    return new Promise(function (resolve, reject) {
        // 異步請求
        Y.io('/userid/1', {
            on: {
                success: function (id, res) {
                    var o = JSON.parse(res);
                    if (o.status === 1) {
                        resolve(o.id);
                    } else {
                        // 請求失敗陷嘴,返回錯誤信息
                        reject(o.errorMsg);
                    }
                }
            }
        });
    });
}

getUserId().then(function (id) {
    // do sth with id
}, function (error) {
    console.log(error);
});

如對Promise的使用尚不了解,推薦閱讀JavaScript Promise Cookbook中文版

在掌握了Promise的使用后间坐,推薦繼續(xù)閱讀Promise規(guī)范Promise A+ 規(guī)范

2.Promise實現(xiàn)原理

理解實現(xiàn)原理過程中灾挨,閱讀了不少相關(guān)文章,其中剖析 Promise 之基礎(chǔ)篇JS Promise的實現(xiàn)原理這兩篇文章竹宋,個人認為質(zhì)量較高劳澄。本文對原理的理解,也是基于這兩篇文章逝撬。

借用MDN的圖浴骂,看看Promise的狀態(tài)遷移,每個 Promise 存在三個互斥狀態(tài):pending宪潮、fulfilled溯警、rejected,它們之間的關(guān)系是:


2.1 Promise簡易實現(xiàn)

最初我是閱讀剖析 Promise 之基礎(chǔ)篇來學(xué)習(xí)的狡相,文中初始實現(xiàn)了一個簡易版的Promise

function Promise(fn) {
    var state = 'pending',
        value = null,
        deferreds = [];

    this.then = function (onFulfilled) {
        if (state === 'pending') {
            deferreds.push(onFulfilled);
            return this;
        }
        onFulfilled(value);
        return this;
    };

    function resolve(newValue) {
        value = newValue;
        state = 'fulfilled';
        setTimeout(function () {
            deferreds.forEach(function (deferred) {
                deferred(value);
            });
        }, 0);
    }

    fn(resolve);
}

function getUserId() {
    return new Promise(function (resolve) {
        resolve(123);
    });
}

getUserId().then(function (id) {
        console.log('do sth with', id);
    });

這一版本的實現(xiàn)梯轻,還是很好理解的。

  • Promise初始狀態(tài)為pending尽棕、value為null喳挑、延遲隊列為空。并且作為函數(shù)作用域的變量滔悉,不向外暴露
  • 傳入Promise的函數(shù)fn立即執(zhí)行伊诵,將resolve傳入fn,fn執(zhí)行完成調(diào)用resolve
  • resolve被定義為一個內(nèi)部函數(shù)回官,使用閉包方式來訪問value曹宴、state、deferreds歉提。遵循Promise規(guī)范笛坦,resolve方法中区转,采用異步方式執(zhí)行延遲隊列的方法
  • promise對象上添加then方法,當前promise對象狀態(tài)為pending時版扩,將通過then方法注冊的新方法废离,添加到延遲隊列;當前promise對象狀態(tài)為完成時礁芦,執(zhí)行注冊的方法

2.2 串行Promise

上述簡易版本的實現(xiàn)蜻韭,相信理解起來無壓力。但是在理解剖析 Promise 之基礎(chǔ)篇文中串行Promise時宴偿,著實費了一番腦筋湘捎。

function getUserId() {
    return new Promise(function (resolve) {
        window.setTimeout(function () {
            resolve(9876);
        });
    });
}

function getUserMobileById(id) {
    return new Promise(function (resolve) {
      console.log('start to get user mobile by id:', id);
        window.setTimeout(function () {
            resolve(13810001000);
        });
    });
}

getUserId()
    .then(getUserMobileById)
    .then(function (mobile) {
        console.log('do sth with', mobile);
    });


function Promise(fn) {
    var state = 'pending',
        value = null,
        deferreds = [];
        
    this.then = function (onFulfilled) {
        return new Promise(function (resolve) {
            handle({
                onFulfilled: onFulfilled || null,
                resolve: resolve
            });
        });
    };

    function handle(deferred) {
        if (state === 'pending') {
            deferreds.push(deferred);
            return;
        }
    
        var ret = deferred.onFulfilled(value);
        deferred.resolve(ret);
    }
    
    function resolve(newValue) {
        if (newValue && (typeof newValue === 'object' || typeof newValue === 'function')) {
            var then = newValue.then;
            if (typeof then === 'function') {
                then.call(newValue, resolve);
                return;
            }
        }
        state = 'fulfilled';
        value = newValue;
        setTimeout(function () {
            deferreds.forEach(function (deferred) {
                handle(deferred);
            });
        }, 0);
    }

    fn(resolve);
}

初看上述代碼時,跟著原文敘述窄刘,沒能徹底理解其執(zhí)行流程。通過不斷的斷點調(diào)試舷胜,才最終理解娩践。在這一過程中,明白了下面幾點

  • Promise的執(zhí)行過程可以分為兩個階段烹骨,即初始時注冊階段和完成時的resolve階段
  • 初始時翻伺,通過promise.then注冊的方法,保存在promise對象的延遲隊列沮焕。每次調(diào)用then方法吨岭,返回一個新promis實例,作為鏈式調(diào)用的橋接峦树,這類promise可以乘坐bridge promise
  • 注冊的方法執(zhí)行完辣辫,執(zhí)行resolve時。從當前promise對象延遲隊列取出注冊的方法繼續(xù)執(zhí)行魁巩。當注冊的方法生成一個新promise實例時急灭,調(diào)用then方法注冊到對應(yīng)的延遲隊列中;否則依次resolve鏈式調(diào)用中相應(yīng)的promise實例

將上述過程表達如下

理解上述執(zhí)行流程谷遂,再為上述實現(xiàn)加上錯誤執(zhí)行過程和異常處理

function Promise(fn) {
    var state = 'pending',
        value = null,
        deferreds = [];

    this.then = function (onFulfilled, onRejected) {
        return new Promise(function (resolve, reject) {
            handle({
                onFulfilled: onFulfilled || null,
                onRejected: onRejected || null,
                resolve: resolve,
                reject: reject
            });
        });
    };

    function handle(deferred) {
        if (state === 'pending') {
            deferreds.push(deferred);
            return;
        }

        var cb = state === 'fulfilled' ? deferred.onFulfilled : deferred.onRejected,
                ret;
        if (cb === null) {
            cb = state === 'fulfilled' ? deferred.resolve : deferred.reject;
            cb(value);
            return;
        }
        try {
            ret = cb(value);
            deferred.resolve(ret);
        } catch (e) {
            deferred.reject(e);
        }
    }

    function resolve(newValue) {
        if (newValue && (typeof newValue === 'object' || typeof newValue === 'function')) {
            var then = newValue.then;
            if (typeof then === 'function') {
                then.call(newValue, resolve, reject);
                return;
            }
        }
        state = 'fulfilled';
        value = newValue;
        finale();
    }

    function reject(reason) {
        state = 'rejected';
        value = reason;
        finale();
    }

    function finale() {
        setTimeout(function () {
            deferreds.forEach(function (deferred) {
                handle(deferred);
            });
        }, 0);
    }

    fn(resolve, reject);
}

3.更好的實現(xiàn)

通過閱讀剖析 Promise 之基礎(chǔ)篇葬馋,跟進上述代碼執(zhí)行過程,相信已經(jīng)可以理解Promise實現(xiàn)原理肾扰。

在理解過程中畴嘶,讓我思考,上述執(zhí)行流程到底是哪里不易理解集晚,有沒有更好的實現(xiàn)呢窗悯?

在上述的實現(xiàn)中,關(guān)鍵方法resolve被定義在Promise中作為內(nèi)部函數(shù)甩恼,通過閉包獲取promise對象的變量的引用蟀瞧。再將回調(diào)函數(shù)和resolve方法注冊到延遲隊列沉颂,通過resolve完成了鏈式的回調(diào)。這一過程隱式調(diào)用太多悦污,不好理解铸屉。

繼續(xù)閱讀其他文章,認為這篇JS Promise的實現(xiàn)原理中有更好的實現(xiàn)切端。其完整實現(xiàn)可以查看這里彻坛。

3.1 代碼結(jié)構(gòu)

構(gòu)造函數(shù)定義如下

function Promise(resolver) {
    this._status = 'pending';

    this._doneCallbacks = [];
    this._failCallbacks = [];

    resolver(resolve, reject);
    ...
}

在 promise 對象中定義了成功回調(diào)和失敗回調(diào)的兩個數(shù)組,數(shù)組中的每一項是通過內(nèi)部封裝的閉包函數(shù)調(diào)用的結(jié)果踏枣,也是一個函數(shù)昌屉。這個函數(shù)可以訪問到內(nèi)部調(diào)用閉包時傳遞的 promise 對象,因此通過這種方式也可以訪問到我們需要的下一個 promise 以及其關(guān)聯(lián)的成功茵瀑、失敗回調(diào)的引用间驮。在then方法中增加閉包調(diào)用以及為前一個 promise 對象保存引用。

Promise.prototype.then = function(onResolve, onReject) {
    var promise = new Promise(function() {});

    this._doneCallbacks.push(makeCallback(promise, onResolve, 'resolve'));
    this._failCallbacks.push(makeCallback(promise, onReject, 'reject'));

    return promise;
}

then方法中調(diào)用的makeCallback即上面說到的閉包函數(shù)马昨。調(diào)用時會把 promise 對象以及相應(yīng)的回調(diào)傳遞進去竞帽,返回一個新的函數(shù)。前一個 promise 對象持有返回函數(shù)的引用鸿捧,這樣在調(diào)用返回函數(shù)時屹篓,在函數(shù)內(nèi)部就可以訪問到 promise 對象以及回調(diào)函數(shù)了。

function makeCallback(promise, callback, action) {
    return function promiseCallback(value) {
        ...
    };
}

resolve和reject函數(shù)的實現(xiàn)相對簡單

function resolve(promise, data) {
        if (promise._status !== 'pending') {
            return;
        }

        promise._status = 'fullfilled';
        promise._value = data;

        run(promise);
    }
function reject(promise, reason) {
        if (promise._status !== 'pending') {
            return;
        }

        promise._status = 'rejected';
        promise._value = reason;

        run(promise);
    }

function run(promise) {
        // `then`方法中也會調(diào)用匙奴,所以此處仍需做一次判斷
        if (promise._status === 'pending') {
            return;
        }

        var value = promise._value;
        var callbacks = promise._status === 'fullfilled'
            ? promise._doneCallbacks
            : promise._failCallbacks;

        // Promise需要異步操作
        setTimeout(function () {
            for (var i = 0, len = callbacks.length; i < len; i++) {
                callbacks[i](value);
            }
        });

        // 每個promise只能被執(zhí)行一次堆巧。雖然`_doneCallbacks`和`_failCallbacks`用戶不應(yīng)該直接訪問,
        // 但還是可以訪問到泼菌,保險起見谍肤,做清空處理。
        promise._doneCallbacks = [];
        promise._failCallbacks = [];
    }

3.2 完整實現(xiàn)

除上述基本方法外灶轰,文中還實現(xiàn)了常用靜態(tài)方法如Promise.all,Promise.race谣沸,完整代碼如下

/**
     * Promise對象的內(nèi)部狀態(tài)
     *
     * @type {Object}
     */
    var Status = {
        PENDING: 'pending',
        FULLFILLED: 'resolved',
        REJECTED: 'rejected'
    };

    function empty() {}

    /**
     * Promise構(gòu)造函數(shù)
     *
     * @constructor
     * @param {Function} resolver 此Promise對象管理的任務(wù)
     */
    function Promise(resolver) {
        // ES6原生的Promise構(gòu)造函數(shù)中,若不通過`new`調(diào)用Promise的構(gòu)造函數(shù)笋颤,會拋出TypeError異常乳附。此處與其一致
        if (!(this instanceof Promise)) {
            throw new TypeError('TypeError: undefined is not a promise');
        }

        // ES6原生的Promise構(gòu)造函數(shù)中,若無作為函數(shù)的resolver參數(shù)伴澄,會拋出TypeError異常赋除。此處與其一致
        if (typeof resolver !== 'function') {
            throw new TypeError('TypeError: Promise resolver undefined is not a function');
        }

        /**
         * Promise對象內(nèi)部的狀態(tài),初始為`pending`非凌。狀態(tài)只能由`pending`到`fullfilled`或`rejected`
         *
         * @type {string}
         */
        this._status = Status.PENDING;

        /**
         * Promise對象resolved/rejected后擁有的data/reason
         *
         *  - 此處保存此值是為了當一個Promise對象被resolved或rejected后举农,繼續(xù)對其調(diào)用`then`添加任務(wù),后續(xù)處理仍能獲得當前Promise的值
         *
         * @type {Mixed}
         */
        this._value;

        /**
         * 當前Promise被resolved/rejected后敞嗡,需處理的任務(wù)
         *
         *  - 由于同一個Promise對象可以調(diào)用多次`then`方法颁糟,以添加多個并行任務(wù)航背,所以此處是一個數(shù)組
         *
         * @type {Array.<Function>}
         */
        this._doneCallbacks = [];
        this._failCallbacks = [];

        var promise = this;
        resolver(
            function (data) {
                resolve(promise, data);
            },
            function (reason) {
                reject(promise, reason);
            }
        );
    }

    Promise.prototype = {

        constructor: Promise,

        /**
         * Promise的`then`方法
         *
         * @param {Function|Mixed} onResolve 當前Promise對象被resolved后,需處理的任務(wù)
         * @param {Function|Mixed} onReject 當前Promise對象被rejected后棱貌,需處理的任務(wù)
         * @return {Promise} 返回一個新的Promise對象玖媚,用于鏈式操作
         */
        then: function (onResolve, onReject) {
            var promise = new Promise(empty);

            this._doneCallbacks.push(makeCallback(promise, onResolve, 'resolve'));
            this._failCallbacks.push(makeCallback(promise, onReject, 'reject'));

            // 如果在一個已經(jīng)被fullfilled或rejected的promise上調(diào)用then,則需要直接執(zhí)行通過then注冊的回調(diào)函數(shù)
            run(this);

            return promise;
        },

        /**
         * Promise的`done`方法
         *
         * @param {Function|Mixed} onResolve 當前Promise對象被resolved后婚脱,需處理的任務(wù)
         * @return {Promise} 返回一個新的Promise對象今魔,用于鏈式操作
         */
        done: function (onResolve) {
            return this.then(onResolve, null);
        },

        /**
         * Promise的`fail`方法
         *
         * @param {Function|Mixed} onReject 當前Promise對象被rejected后,需處理的任務(wù)
         * @return {Promise} 返回一個新的Promise對象障贸,用于鏈式操作
         */
        fail: function (onReject) {
            return this.then(null, onReject);
        },

        /**
         * Promise的`catch`方法
         *
         * @param {Function|Mixed} onFail 當前Promise對象被rejected后错森,需處理的任務(wù)
         * @return {Promise} 返回一個新的Promise對象,用于鏈式操作
         */
        catch: function (onFail) {
            return this.then(null, onFail);
        }
    };

    /**
     * 創(chuàng)建一個Promise對象篮洁,并用給定值resolve它
     *
     * @param {Mixed} value 用于resolve新創(chuàng)建的Promise對象的值
     * @return {Promise} 返回一個新的Promise對象涩维,用于鏈式操作
     */
    Promise.resolve = function (value) {
        var promise = new Promise(empty);
        resolve(promise, value);
        return promise;
    };

    /**
     * 創(chuàng)建一個Promise對象,并用給定值reject它
     *
     * @param {Mixed} reason 用于reject新創(chuàng)建的Promise對象的值
     * @return {Promise} 返回一個新的Promise對象嘀粱,用于鏈式操作
     */
    Promise.reject = function (reason) {
        var promise = new Promise(empty);
        reject(promise, reason);
        return promise;
    };

    /**
     * 返回一個promise激挪,這個promise在iterable中的任意一個promise被解決或拒絕后,
     * 立刻以相同的解決值被解決或以相同的拒絕原因被拒絕
     *
     * @param {Iterable.<Promise|Mixed>} iterable 一組Promise對象或其它值
     * @return {Promise} 返回一個新的Promise對象锋叨,用于鏈式操作
     */
    Promise.race = function (iterable) {
        if (!iterable || !iterable.hasOwnProperty('length')) {
            throw new TypeError('TypeError: Parameter `iterable` must be a iterable object');
        }

        var promise = new Promise(empty);
        for (var i = 0, len = iterable.length; i < len; i++) {
            var iterate = iterable[i];
            if (!(iterate instanceof Promise)) {
                iterate = Promise.resolve(iterate);
            }

            iterate.then(resolveRaceCallback, rejectRaceCallback);
        }

        var settled = false;

        function resolveRaceCallback(data) {
            if (settled) {
                return;
            }

            settled = true;
            resolve(promise, data);
        }

        function rejectRaceCallback(reason) {
            if (settled) {
                return;
            }

            settled = true;
            reject(promise, reason);
        }
    };

    /**
     * 返回一個promise,該promise會在iterable參數(shù)內(nèi)的所有promise都被解決后被解決
     *
     * @param {Iterable.<Promise|Mixed>} iterable 一組Promise對象或其它值
     * @return {Promise} 返回一個新的Promise對象宛篇,用于鏈式操作
     */
    Promise.all = function (iterable) {
        if (!iterable || !iterable.hasOwnProperty('length')) {
            throw new TypeError('TypeError: Parameter `iterable` must be a iterable object');
        }

        var promise = new Promise(empty);
        var length = iterable.length;
        for (var i = 0; i < length; i++) {
            var iterate = iterable[i];
            if (!(iterate instanceof Promise)) {
                iterate = Promise.resolve(iterate);
            }

            iterate.then(makeAllCallback(iterate, i, 'resolve'), makeAllCallback(iterate, i, 'reject'));
        }

        var result = [];
        var count = 0;

        function makeAllCallback(iterate, index, action) {
            return function (value) {
                if (action === 'reject') {
                    reject(promise, value);
                    return;
                }

                result[index] = value;

                if (++count === length) {
                    resolve(promise, result);
                }
            }
        }
    };

    /**
     * 返回一個Deferred對象娃磺,包含一個新創(chuàng)建的Promise對象,以及`resolve`和`reject`方法
     *
     * @return {Deferred}
     */
    Promise.defer = function () {
        var promise = new Promise(empty);

        return {
            promise: promise,
            resolve: function (data) {
                resolve(promise, data);
            },
            reject: function (reason) {
                reject(promise, reason);
            }
        };
    };

    function run(promise) {
        // `then`方法中也會調(diào)用叫倍,所以此處仍需做一次判斷
        if (promise._status === Status.PENDING) {
            return;
        }

        var value = promise._value;
        var callbacks = promise._status === Status.FULLFILLED
            ? promise._doneCallbacks
            : promise._failCallbacks;

        // Promise需要異步操作
        setTimeout(function () {
            for (var i = 0, len = callbacks.length; i < len; i++) {
                callbacks[i](value);
            }
        });

        // 每個promise只能被執(zhí)行一次偷卧。雖然`_doneCallbacks`和`_failCallbacks`用戶不應(yīng)該直接訪問,
        // 但還是可以訪問到吆倦,保險起見听诸,做清空處理。
        promise._doneCallbacks = [];
        promise._failCallbacks = [];
    }

    function resolve(promise, data) {
        if (promise._status !== Status.PENDING) {
            return;
        }

        promise._status = Status.FULLFILLED;
        promise._value = data;

        run(promise);
    }

    function reject(promise, reason) {
        if (promise._status !== Status.PENDING) {
            return;
        }

        promise._status = Status.REJECTED;
        promise._value = reason;

        run(promise);
    }

    function makeCallback(promise, callback, action) {
        return function promiseCallback(value) {
            // 如果傳遞了callback蚕泽,則使用前一個promise傳遞過來的值作為參數(shù)調(diào)用callback晌梨,
            // 并根據(jù)callback的調(diào)用結(jié)果來處理當前promise
            if (typeof callback === 'function') {
                var x;
                try {
                    x = callback(value);
                }
                catch (e) {
                    // 如果調(diào)用callback時拋出異常,則直接用此異常對象reject當前promise
                    reject(promise, e);
                }

                // 如果callback的返回值是當前promise须妻,為避免造成死循環(huán)仔蝌,需要拋出異常
                // 根據(jù)Promise+規(guī)范,此處應(yīng)拋出TypeError異常
                if (x === promise) {
                    var reason = new TypeError('TypeError: The return value could not be same with the promise');
                    reject(promise, reason);
                }
                // 如果返回值是一個Promise對象荒吏,則當返回的Promise對象被resolve/reject后敛惊,再resolve/reject當前Promise
                else if (x instanceof Promise) {
                    x.then(
                        function (data) {
                            resolve(promise, data);
                        },
                        function (reason) {
                            reject(promise, reason);
                        }
                    );
                }
                else {
                    var then;
                    (function resolveThenable(x) {
                        // 如果返回的是一個Thenable對象(此處邏輯有點坑,參照Promise+的規(guī)范實現(xiàn))
                        if (x && (typeof x === 'object'|| typeof x === 'function')) {
                            try {
                                then = x.then;
                            }
                            catch (e) {
                                reject(promise, e);
                                return;
                            }

                            if (typeof then === 'function') {
                                // 調(diào)用Thenable對象的`then`方法時绰更,傳遞進去的`resolvePromise`和`rejectPromise`方法(及下面的兩個匿名方法)
                                // 可能會被重復(fù)調(diào)用瞧挤。但Promise+規(guī)范規(guī)定這兩個方法有且只能有其中的一個被調(diào)用一次锡宋,多次調(diào)用將被忽略。
                                // 此處通過`invoked`來處理重復(fù)調(diào)用
                                var invoked = false;
                                try {
                                    then.call(
                                        x,
                                        function (y) {
                                            if (invoked) {
                                                return;
                                            }
                                            invoked = true;

                                            // 避免死循環(huán)
                                            if (y === x) {
                                                throw new TypeError('TypeError: The return value could not be same with the previous thenable object');
                                            }

                                            // y仍有可能是thenable對象特恬,遞歸調(diào)用
                                            resolveThenable(y);
                                        },
                                        function (e) {
                                            if (invoked) {
                                                return;
                                            }
                                            invoked = true;

                                            reject(promise, e);
                                        }
                                    );
                                }
                                catch (e) {
                                    // 如果`resolvePromise`和`rejectPromise`方法被調(diào)用后执俩,再拋出異常,則忽略異常
                                    // 否則用異常對象reject此Promise對象
                                    if (!invoked) {
                                        reject(promise, e);
                                    }
                                }
                            }
                            else {
                                resolve(promise, x);
                            }
                        }
                        else {
                            resolve(promise, x);
                        }
                    }(x));
                }
            }
            // 如果未傳遞callback鸵鸥,直接用前一個promise傳遞過來的值resolve/reject當前Promise對象
            else {
                action === 'resolve'
                    ? resolve(promise, value)
                    : reject(promise, value);
            }
        };
    }

4.最后

相比奠滑,剖析 Promise 之基礎(chǔ)篇JS Promise的實現(xiàn)原理文中代碼結(jié)構(gòu)要更加清晰妒穴,對閉包的調(diào)用更為易讀宋税。

像resolve(),reject(),run(),makeCallback()這幾個工具方法,沒有直接定義到Promise構(gòu)造函數(shù)中讼油,作為內(nèi)部函數(shù)杰赛,產(chǎn)生隱式的閉包調(diào)用,而是提出來作為公共方法矮台,通過參數(shù)傳遞方式將promise對象傳入方法乏屯。使得代碼可讀性好,容易理解瘦赫。

同時辰晕,在學(xué)習(xí)的過程中,讀到這篇奇舞團翻譯的Bluebird 是如何做到比原生實現(xiàn)更快的确虱?

文中提到了Bluebird相對于原生Promise的幾個優(yōu)化點

  • 函數(shù)中的對象分配最小化
  • 減小對象體積
  • 可選特性懶重寫

其第一點就是避免在Promise構(gòu)造函數(shù)中含友,直接定義其他內(nèi)部函數(shù)。除開前面提到的可讀性問題校辩,避免這樣做窘问,可以避免每次創(chuàng)建Promise對象時,同時創(chuàng)建全新的內(nèi)部函數(shù)對象宜咒。

參考文章

如果覺得有幫助儿咱,可以掃描二維碼對我打賞,謝謝

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末倍阐,一起剝皮案震驚了整個濱河市概疆,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌峰搪,老刑警劉巖岔冀,帶你破解...
    沈念sama閱讀 217,509評論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡使套,警方通過查閱死者的電腦和手機罐呼,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,806評論 3 394
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來侦高,“玉大人嫉柴,你說我怎么就攤上這事》钋海” “怎么了计螺?”我有些...
    開封第一講書人閱讀 163,875評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長瞧壮。 經(jīng)常有香客問我登馒,道長,這世上最難降的妖魔是什么咆槽? 我笑而不...
    開封第一講書人閱讀 58,441評論 1 293
  • 正文 為了忘掉前任陈轿,我火速辦了婚禮,結(jié)果婚禮上秦忿,老公的妹妹穿的比我還像新娘麦射。我一直安慰自己,他們只是感情好灯谣,可當我...
    茶點故事閱讀 67,488評論 6 392
  • 文/花漫 我一把揭開白布潜秋。 她就那樣靜靜地躺著,像睡著了一般胎许。 火紅的嫁衣襯著肌膚如雪半等。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,365評論 1 302
  • 那天呐萨,我揣著相機與錄音,去河邊找鬼莽囤。 笑死谬擦,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的朽缎。 我是一名探鬼主播惨远,決...
    沈念sama閱讀 40,190評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼话肖!你這毒婦竟也來了北秽?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,062評論 0 276
  • 序言:老撾萬榮一對情侶失蹤最筒,失蹤者是張志新(化名)和其女友劉穎贺氓,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體床蜘,經(jīng)...
    沈念sama閱讀 45,500評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡辙培,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,706評論 3 335
  • 正文 我和宋清朗相戀三年蔑水,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片扬蕊。...
    茶點故事閱讀 39,834評論 1 347
  • 序言:一個原本活蹦亂跳的男人離奇死亡搀别,死狀恐怖歇父,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情再愈,我是刑警寧澤,帶...
    沈念sama閱讀 35,559評論 5 345
  • 正文 年R本政府宣布单刁,位于F島的核電站,受9級特大地震影響羔飞,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜檐春,卻給世界環(huán)境...
    茶點故事閱讀 41,167評論 3 328
  • 文/蒙蒙 一逻淌、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧疟暖,春花似錦卡儒、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,779評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至欣舵,卻和暖如春擎鸠,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背缘圈。 一陣腳步聲響...
    開封第一講書人閱讀 32,912評論 1 269
  • 我被黑心中介騙來泰國打工劣光, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人糟把。 一個月前我還...
    沈念sama閱讀 47,958評論 2 370
  • 正文 我出身青樓绢涡,卻偏偏與公主長得像,于是被迫代替她去往敵國和親遣疯。 傳聞我的和親對象是個殘疾皇子雄可,可洞房花燭夜當晚...
    茶點故事閱讀 44,779評論 2 354

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