ES6 -- 异步处理 Promise

console.log(100);
setTimeout(() => {
    console.log(200);
}, 0);
console.log(300);

// 100  300  200

事件循环

JS运行的环境称之为宿主环境。

执行栈:call stack,一个数据结构,用于存放各种函数的执行环境,每一个函数执行之前,它的相关信息会加入到执行栈。函数调用之前,创建执行环境,然后加入到执行栈;函数调用之后,销毁执行环境。

JS永远执行的是执行栈的最顶部。

call stack.png
function a() {
    console.log("a");
    b();
}

function b() {
    console.log("b");
    c();
}

function c() {
    console.log("c");
}

console.log("global");
a();

// global   a  b  c
stack.png

递归:

// 1  1  2  3  5  8 斐波拉契数列
function getFeiBo(n) {
    if(n === 1 || n === 2) {
        return 1;
    }

    return getFeiBo(n - 1) + getFeiBo(n - 2);
}

getFeiBo(4); // 3
斐波数列.png

异步函数:某些函数不会立即执行,需要等到某个时机到达后才会执行,这样的函数,称之为 异步函数,比如 事件处理函数。异步函数的执行时机,会被宿主环境控制。

浏览器宿主环境中包含5个线程:

  1. JS引擎:负责执行执行栈的最顶部代码
  2. GUI线程: 负责渲染页面
  3. 事件监听线程:负责监听各种事件
  4. 计时线程:负责计时
  5. 网络线程:负责网络通信

当上面的线程发生了某些事情,如果该线程发现,这件事情有处理程序,它将该处理程序加入一个叫做事件队列的内存。当JS引擎发现,执行栈中已经没有了任何内容后,会将事件队列中的第一个函数加入到执行栈中执行。

JS引擎对事件队列的取出执行方式,以及与宿主环境的配合,称之为事件循环。

事件队列在不同的宿主环境中有所差异,大部分宿主环境会将事件队列进行细分。在浏览器中,事件队列分为两种:

  • 宏任务(队列):macroTask,计时器结束的回调、事件回调、http回调等待绝大部分异步函数进行宏队列。

  • 微任务(队列):MutationObserver,Promise产生的回调进入微队列。

当执行栈清空时,JS引擎首先会将微任务中的所有任务依次执行结束,如果没有微任务,则执行宏任务。

<ul id="containor"></ul>

<button id="btn">点击</button>
<script>
    let count = 1;
    const ul = document.getElementById("containor");
    const btn = document.getElementById("btn");

    btn.onclick = function() {
        var li = document.createElement("li");
        li.innerText = count ++;
        ul.appendChild(li);
        setTimeout(() => {
            console.log("触发定时器");
        }, 0);
        console.log("添加了一个li");
    }

    // 监听
    const observer = new MutationObserver(() => {
        // 当监听的dom元素发生变化时,运行的回调函数
        console.log("ul发生了变化");
    })

    observer.observe(ul, {
        attributes: true, // 监听属性的变化
        childList: true, // 监听子元素的变化
        subtree: true, // 监听子树的变化
    })

    // 取消监听使用
    // observer.disconnect();

    // 触发结果为:
    // 添加了一个li
    // ul发生了变化
    // 触发定时器
</script>
event loop.png

事件和回调函数的缺陷(回调地狱问题)

我们习惯于使用传统的回调或事件处理来解决异步问题。

  • 事件:某个对象的属性是一个函数,当发生某一件事时,运行该函数。
dom.onclick = function() {}
  • 回调:运行某个函数以实现某个功能的时候,传入一个函数作为参数,当发生某件事的时候,会运行该函数。
dom.addEventListener("click", function() {})

本质上,事件和回调并没有本质的区别,只是把函数放置的位置不同而已。

一直以来,该模式都运作良好。

直到前端工程越来越复杂。。。

目前,该模式主要面临以下两个问题:
1. 回调地狱:某个异步操作需要等待之前的异步操作完成,无论用回调还是事件,都会陷入不断的嵌套。
回调地狱1:

<div class="wrapper">
    <button id="btn1">按钮1:给按钮2注册点击事件</button>
    <button id="btn2">按钮2:给按钮3注册点击事件</button>
    <button id="btn3">按钮3:点击弹出hello</button>
</div>
<script>
    const btn1 = document.getElementById("btn1"),
        btn2 = document.getElementById("btn2"),
        btn3 = document.getElementById("btn3");

    btn1.addEventListener("click", function() {           
        btn2.addEventListener("click", function() {
            btn3.addEventListener("click", function() {
                alert("hello");
            })
        })
    })
    
</script>

回调地狱2:

// 张三心中有三个女神
// 有一天,张三决定向第一个女神表白,如果女神拒绝,则向第二个女神表白,直到所有的女神都拒绝,或有一个女神同意为止
// 用代码实现以上场景

function biaobai(god, callback) {
    console.log(`张三向女神【${god}】发出了表白短信`);
    setTimeout(() => {
        if (Math.random() < 0.3) {
            // 同意
            callback(true);
        } else {
            // 拒绝
            callback(false);
        }
    }, 1000);
}

biaobai("女神1", result => {
    if (result) {
        console.log("女神1答应了,张三很开心!");
    } else {
        console.log("女神1拒绝了,然后向女神2表白!");

        biaobai("女神2", result => {
            if (result) {
                console.log("女神2答应了,张三很开心!");
            } else {
                console.log("女神2拒绝了,然后向女神3表白!");

                biaobai("女神3", result => {
                    if (result) {
                        console.log("女神3答应了,张三很开心!");
                    } else {
                        console.log("女神3拒绝了,张三表示生无可恋!");
                    }
                })
            }
        })
    }
})

2. 异步之间的练习:某个异步操作需要等待多个异步操作的结果,对这种联系的处理,会让代码的复杂剧增。

// 李四心中有20个女神,他决定同时给20个女神表白,如果有女神同意,就拒绝其他的女神;
// 并且,当所有的女神回复完成后,他要把所有的回复都记录到日志中进行分析
// 用代码模拟以上场景

function biaobai(god, callback) {
    console.log(`张三向女神【${god}】发出了表白短信`);
    setTimeout(() => {
        if (Math.random() < 0.05) {
            // 同意
            callback(true);
        } else {
            // 拒绝
            callback(false);
        }
    }, Math.floor(Math.random() * (3000 - 1000) + 1000));
}

let argeeGod = null; // 同意的第一个女神

const results = []; // 记录回复结果

for(let i = 0; i <= 20; i ++) {
    biaobai(`女神${i}`, result => {
        results.push(result);
        
        if(result) {
            console.log(`女神${i}同意了`);

            if(argeeGod) {
                console.log(`张三回复女神${i}:不好意思,发错了!`)
            }else{
                argeeGod = `女神${i}`;
                console.log(`张三回复女神${i}:那和我在一起吧!`);
            }
        }else{
            console.log(`女神${i}拒绝了`);
        }

        if(results.length === 20) {
            console.log("记录", results);
        }
    })
}

异步处理的通用模型

ES官方参考了大量的异步场景,总结出了一套异步的通用模型,该模型可以覆盖几乎所有的异步场景,甚至是同步场景。

值得注意的是,为了兼容旧系统,ES6 并不打算抛弃过去的做法,只是基于该模型推出一个全新的API,使用该API,会让异步处理更加的简洁优雅。

理解该API,最重要的,是理解它的异步模型。

1. ES6 将某一件可能发生异步操作的事情,分为两个阶段:unsettled** 和 settled。**

  • unsettled:未决阶段,表示事情还在进行前期的处理,并没有发生通向结果的那件事。

  • settled:已决阶段,事情已经有了一个结果,不管这个结果是好是坏,整件事情无法逆转。

事情总是从 未决阶段 逐步发展到 已决阶段 的。并且,未决阶段 拥有控制何时通向 已决阶段 的能力。

Promise.png

2. ES6 将事情划分为三种状态:pending、resolved、rejected。

  • pending:挂起,处于未决阶段,则表示这件事情还在挂起(最终的结果还没出来)。

  • resolved:已处理,已决阶段的一种状态,表示整件事情已经出现结果,并是一个可以按照正常逻辑进行下去的结果。

  • rejected:已拒绝,已决阶段的一种状态,表示整件事情已经出现结果,并是一个无法按照正常逻辑进行下去的结果,通常用于表示有一个错误。

既然 未决阶段 有权力决定事情的走向,因此,未决阶段 可以决定事情最终的状态!

我们将 把事情变为 resolved 状态的过程叫做:resolve,推向该状态时,可能会传递一些数据。

我们将 把事情变为 rejected 状态的过程叫做:reject,推向该状态时,同样可能会传递一些数据,通常为错误信息。

始终记住,无论是阶段,还是状态,是不可逆的。

  1. 当事情达到已决阶段后,通常需要进行后续处理,不同的已决状态,决定了不同的后续处理。
  • resolved状态:这是一个正常的已决状态,后续处理为 thenable。

  • rejected状态:这是一个非正常的已决状态,后续处理表示为 catchable。

后续处理可能有多个,因此会形成作业队列,这些后续处理会按照顺序,当状态到达后依次执行。

  1. 整个事件称之为 Promise。
promise队列.png

Promise 的基本使用 (处理异步场景)

const pro = new Promise((resolve, reject) => {
    // 未决阶段的处理
    // 通过调用 resolve 函数将 Promise 推向 已决阶段 的 fulfilled 状态
    // 通过调用 reject 函数将 Promise 推向 已决阶段 的 rejected 状态
    // resolve 和 reject 均可以传递最多一个参数,表示推向状态的数据
})

pro.then(data => {
    // 这是 thenable 函数,如果当前的 Promise 已经是 fulfilled状态,该函数会立即执行
    // 如果当前是 未决阶段,则会加入到作业队列,等待到达 fulfilled 状态后执行
    // data 为状态数据
}, err => {
    // 这是 catchable 函数,如果当前的 Promise 已经是 rejected 状态,该函数会立即执行
    // 如果当前是 未决阶段,则会加入到作业队列,等待到达 rejected 状态后执行
    // err 为状态数据
})
const pro = new Promise((resolve, reject) => {
    console.log("未决阶段");

    setTimeout(() => {
        resolve(1);
    },  10000)
    // reject(2);
})

pro.then(data => {
    console.log(data); // 1
}, err => {
    // console.log(err); // 2
})
const pro = new Promise((resolve, reject) => {
    console.log(`张三向女神发出表白短信`);

    setTimeout(() => {
        if(Math.random() < 0.2) {
            resolve(true);
        }else{
            resolve(false);
        }
    },  10000)
    // reject(2);
})

pro.then(data => {
    console.log(data); // 1
}, err => {
    // console.log(err); // 2
})
<script src="./回调地狱问题/ajax.js"></script>
<script>
const pro = new Promise((resolve, reject) => {
    ajax({
        url: ".//json/students.json?name=李华",
        success(data) {
            resolve(data);
        },
        error(err) {
            reject(err);
        }
    })
})

pro.then(data => {
    console.log(data); 
}, err => {
    console.log(err); 
})
</script>
function toData(obj) {
    if (obj === null) {
        return obj;
    }
    let arr = [];
    for (let i in obj) {
        let str = i + "=" + obj[i];
        arr.push(str);
    }
    return arr.join("&");
}

function ajax(obj) {
    return new Promise((resolve, reject) => {
        // 指定提交方式的默认值
        obj.type = obj.type || "get";
        // 设置是否异步,默认为true(异步)
        obj.asycn = obj.asycn || true;
        // 设置数据的默认值
        obj.data = obj.data || null;

        // 根据不同的浏览器创建XHR对象
        let xhr = null;
        if (window.XMLHttpRequest) {
            // 非IE浏览器
            xhr = new XMLHttpRequest();
        } else {
            // IE浏览器
            xhr = new ActiveXObject("Microsoft.XMLHTTP");
        }

        // 区分get和post,发送HTTP请求
        if (obj.type === "post") {
            xhr.open(obj.type, obj.url, obj.asycn);
            xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
            let data = toData(obj.data);
            xhr.send(data);
        } else {
            let url = obj.url + "?" + toData(obj.data);
            xhr.open(obj.type, url, obj.asycn);
            xhr.send();
        }

        // 接收返回过来的数据
        xhr.onreadystatechange = function () {
            if (xhr.readyState === 4) {
                if (xhr.status >= 200 && xhr.status < 300 || xhr.status == 304) {

                    resolve(JSON.parse(xhr.responseText));

                } else {
                    reject(xhr.status);
                }
            }
        }
    })
}

ajax({
    url: ".//json/students.json?name=李华"
}).then(data => {
    console.log(data);
}, err => {
    console.log(err); // 404
})
  • 细节:

1. 未决阶段 的处理函数是 同步的,会立即执行。

2. thenable 和 catchable 函数是 异步的,就算是立即执行,也会加入到事件队列中等待执行,并且,加入的队列是微队列。

const pro = new Promise((resolve, reject) => {
    setTimeout(() => {
        console.log(123);
    }, 0)

    resolve(999);

    console.log(456);

    
})

pro.then(data => {
    console.log(data);
})

3. pro.then 可以只添加 thenable 函数,pro.catch 可以单独添加 catchable 函数。

const pro = new Promise((resolve, reject) => {
    setTimeout(() => {
        console.log(123);
    }, 0)

    resolve(999);

    console.log(456);    
})

pro.then(data => {
    console.log(data);
})

pro.catch(err => {
    console.log(err);
})

4. 在 未决阶段 的处理函数中,如果发生未捕获的错误,会将状态推向 rejected,并会被 catchable 捕获。

const pro = new Promise((resolve, reject) => {
    setTimeout(() => {
        console.log(123);
    }, 0)

    // resolve(999);

    reject(222);

    console.log(456);    
})

pro.then(data => {
    console.log(data);
})

pro.catch(err => {
    console.log(err);
})

5. 一旦状态推向了 已决阶段,无法再对状态做任何更改。

  1. Promise 并没有消除回调,只是让回调变得可控。

Promise 的串联

<script src="./封装ajax/ajax.js"></script>
<script>
    const pro = ajax({
        url: "./回调地狱问题/json/students.json?name=李"
    })

    const pro2 = pro.then(data => {
        console.log(data);
        for(let i = 0; i < data.length; i ++) {
            if(data[i].name === "李") {
                const cid = data[i].classId;
            }
        }
    })

    console.log(pro2);
</script>
const pro1 = new Promise((resolve, reject) => {
    resolve(1);
})

const pro2 = pro1.then(data => console.log(data * 2));

console.log(pro2);

当后续的Promise需要用到之前的 Promise 的处理结果时,需要Promise 的串联。

Promise 对象中,无论是then方法还是catch方法,它们都具有返回值,返回的是一个全新的Promise对象,它的状态满足下面的规则:

1. 如果当前的Promise是未决的,得到的新的Promise是挂起状态。

2. 如果当前的Promise是已决的,会运行响应的后续处理函数,并将后续处理函数的结果(返回值)作为resolved状态数据,应用到最新的Promise中;如果后续处理函数发生错误,则把返回值作为rejected状态数据,应用到新的Promise中。

const pro1 = new Promise((resolve, reject) => {
    // resolve(1);
    reject(3);
})

const pro2 = pro1.then(data => data * 2, err => err * 3);

pro2.then(data => console.log("data",data), err => console.log("err",err));

// data  9

后续的Promise一定会等到前面的Promise有了后续处理结果后,才会变成已决状态。

const pro1 = new Promise((resolve, reject) => {
    resolve(1);
})

const pro2 = new Promise((resolve, reject) => {
    resolve(2);
})

const pro3 = pro1.then(data => {
    return pro2;
});

pro3.then(data => console.log(data));
// 2

如果前面的Promise的后续处理,返回的是一个Promise,则返回的新的Promise状态和后续处理返回的Promise状态保持一致。

<script src="./封装ajax/ajax.js"></script>
<script>
    const pro = ajax({
        url: "./回调地狱问题/json/students.json?name=李"
    }).then(data => {
        // console.log(data);
        for(let i = 0; i < data.length; i ++) {
            if(data[i].name === "李") {
                return data[i].classId; // 班级id
            }
        }
    }).then(cid => {
        console.log(cid);
        ajax({
            url: "./回调地狱问题/json/classes.json?cid=" + cid
        }).then(data => {
            // console.log(data);
            for(let i = 0; i < data.length; i ++) {
                if(data[i].id === cid) {
                    return data[i].teacherId;
                }
            }
        }).then(tid => {
            // console.log(tid);
            ajax({
                url: "./回调地狱问题/json/teachers.json?id=" + tid
            }).then(data => {
                // console.log(data);
                for(let i = 0; i <data.length; i ++) {
                    if(data[i].id === tid) {
                        return data[i];
                    }
                }
            }).then(data => {
                console.log(data);
            })
        })
    })
</script>
function biaobai(god) {
    return new Promise(resolve => {
        console.log(`张三向${god},发出了表白短信`);

        setTimeout(() => {
            if(Math.random() < 0.3) {
                // 同意
                resolve(true);
            }else{
                resolve(false);
            }
        },500)
    })
}

const gods = ["女神1", "女神2", "女神3", "女神4"];

let pro;
for(let i = 0; i < gods.length; i ++) {
    if(i === 0) {
        pro = biaobai(gods[i]);
    }

    pro = pro.then(data => {
        if(data === undefined) {
            return;
        }else if(data) {
            console.log(`${gods[i]}同意了`);
            return;
        }else{
            console.log(`${gods[i]}拒绝了`);
            if(i < gods.length - 1) {
                return biaobai(gods[i + 1]);
            }
        }
    })
}

Promise 的其他api

原型成员(实例成员)

  • then:注册一个后续处理函数,当 Promise 为 resolved 状态时运行该函数。

  • catch:注册一个后续处理函数,当 Promise 为 rejected 状态时运行该函数。

  • finally:[ES2018]注册一个后续处理函数(无参),当 Promise 为已决时运行该函数。

const pro = new Promise((resolve, reject) => {
    // resolve(1); // then1 1   then1 2   finally1  finally2

    reject(1); // catch1 1   catch1 2   finally1  finally2
})

pro.then(data => console.log("then1", data * 1));
pro.then(data => console.log("then1", data * 2));
pro.catch(data => console.log("catch1", data * 1));
pro.catch(data => console.log("catch1", data * 2));
pro.finally(() => console.log("finally1"));
pro.finally(() => console.log("finally2"));

构造函数成员(静态成员)

1. resolve(数据):该方法返回一个 resolved 状态的 Promise,传递的数据作为状态数据。

特殊情况:如果传递的数据是 Promise,则直接返回传递的 Promise 对象。

const pro = new Promise((resolve, reject) => {
    resolve(1); 
})

const p = Promise.resolve(pro);

console.log(p === pro); // true

2. reject(数据):该方法返回一个 rejected 状态的 Promise ,传递的数据作为状态数据。

const pro = new Promise((resolve, reject) => {
    resolve(1); 
})
// 等效于
const pro1 = Promise.resolve(1);

// const pro = new Promise((resolve, reject) => {
//     reject(1); 
// })
// 等效于
// const pro1 = Promise.reject(1);

3. all(iterable):这个方法返回一个新的 Promise 对象,该 Promise 对象在 iterable 参数对象里所有的 Promise 对象都成功的时候才会触发成功;

function getRandom(min, max) {
    return Math.floor(Math.random() * (max - min)) + min;
}

const proms = [];

for(let i = 0; i < 10; i ++) {
    proms.push(new Promise((resolve, reject) => {
        setTimeout(() => {
            console.log(i, "over");
            resolve(i);
        },getRandom(1000, 5000));
    }))
}

const pro = Promise.all(proms);

pro.then(data => {
    console.log("全部完成", data);
});

console.log(proms);

一旦有任何一个 iterable 里面的 Promise 对象失败则立即触发该 Promise 对象的失败;

这个新的 Promise 对象在触发成功状态以后,会把一个包含 iterable 里所有 Promise 返回值的数组作为成功回调的返回值,顺序跟 iterable 的顺序保持一致;

如果这个新的 Promise 对象触发了失败状态,它会把 iterable 里第一个触发失败的 Promise 对象的错误信息作为它的失败的错误信息,Promise.all 方法常被用于处理多个 Promise 对象的状态集合。

// 李四心中有20个女神,他决定同时给20个女神表白,如果有女神同意,就拒绝其他的女神;
// 并且,当所有的女神回复完成后,他要把所有的回复都记录到日志中进行分析
// 用代码模拟以上场景

function biaobai(god) {
    return new Promise(resolve => {
        console.log(`张三向${god},发出了表白短信`);

        setTimeout(() => {
            if(Math.random() < 0.3) {
                // 同意
                resolve(true);
            }else{
                resolve(false);
            }
        },Math.floor(Math.random() * (3000 - 1000) + 1000))
    })
}

const proms = [];

let argeeGod = null; // 同意的第一个女神

for(let i = 0; i <= 20; i ++) {
    proms.push(biaobai(`女神${i}`).then(data => {
        // console.log(data);
        if(data) {     
            console.log(`女神${i}同意了`);

            if(argeeGod) {
                console.log(`张三回复女神${i}:不好意思,发错了!`);
            }else{
                argeeGod = `女神${i}`;
                console.log(`张三回复女神${i}:那和我在一起吧!`);
            }

            return `女神${i}同意了`;
        }else{
            console.log(`女神${i}拒绝了`);
            return `女神${i}拒绝了`;
        }
    }))
}

Promise.all(proms).then(data => {
    console.log("over", data);
})

4. race(iterable):当 iterable 参数里的任意一个子 Promise 的成功或失败后,父 Promise 马上也会用子 Promise 的成功返回值或失败详情作为参数调用父 Promise 绑定的相应句柄,并返回该 Promise 对象。

function getRandom(min, max) {
    return Math.floor(Math.random() * (max - min)) + min;
}

const proms = [];

for(let i = 0; i < 10; i ++) {
    proms.push(new Promise((resolve, reject) => {
        setTimeout(() => {
            console.log(i, "over");
            resolve(i);
        },getRandom(1000, 5000));
    }))
}

const pro = Promise.race(proms);

pro.then(data => {
    console.log("全部完成", data);
});

console.log(proms);

Promise状态的特点

Promise 异步操作有三种状态:pending(进行中)、fulfilled(已成功)和 rejected(已失败)。除了异步操作的结果,任何其他操作都无法改变这个状态。

Promise 对象只有:从 pending 变为 fulfilled 和从 pending 变为 rejected 的状态改变。只要处于 fulfilled 和 rejected ,状态就不会再变了即 resolved(已定型)。

then 方法接收两个函数作为参数,第一个参数是 Promise 执行成功时的回调,第二个参数是 Promise 执行失败时的回调,两个函数只会有一个被调用。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,590评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 86,808评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,151评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,779评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,773评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,656评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,022评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,678评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,038评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,659评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,756评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,411评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,005评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,973评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,053评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,495评论 2 343

推荐阅读更多精彩内容