js異步

弄懂js異步

講異步之前,我們必須掌握一個基礎知識-event-loop规伐。

我們知道JavaScript的一大特點就是單線程,而這個線程中擁有唯一的一個事件循環(huán)。當然新標準中的web worker涉及到了多線程镐依,但它的原理是利用一個父線程和多個子線程,歸根結底來說js仍逃不過是單線程的事實天试。

JavaScript代碼的執(zhí)行過程中,除了依靠函數(shù)調用棧來搞定函數(shù)的執(zhí)行順序外然低,還依靠任務隊列(task queue)(先進先出)來搞定另外一些代碼的執(zhí)行

  • 一個線程中喜每,事件循環(huán)是唯一的务唐,但是任務隊列可以擁有多個。

  • 任務隊列又分為macro-task(宏任務)與micro-task(微任務)带兜,在最新標準中枫笛,它們被分別稱為task與jobs。

  • macro-task大概包括:script(整體代碼), setTimeout, setInterval, setImmediate, I/O, UI rendering刚照。

  • micro-task大概包括: process.nextTick, Promise, Object.observe(已廢棄), MutationObserver(html5新特性)

  • setTimeout/Promise等我們稱之為任務源刑巧。而進入任務隊列的是他們指定的具體執(zhí)行任務。

//setTimeout本身是一個任務分發(fā)器无畔,他作為一個任務源啊楚,會立即執(zhí)行
setTimeout(function() { 
    //里面的函數(shù)是具體任務,它會延遲執(zhí)行
    console.log('dc');
})

來自不同任務源的任務會進入到不同的任務隊列浑彰。其中setTimeout與setInterval是同源的恭理。

事件循環(huán)的順序,決定了JavaScript代碼的執(zhí)行順序郭变。它從script(整體代碼)開始第一次循環(huán)颜价。之后全局上下文進入函數(shù)調用棧。直到調用棧清空(只剩全局)诉濒,然后執(zhí)行所有的micro-task周伦。當所有可執(zhí)行的micro-task執(zhí)行完畢之后。循環(huán)再次從macro-task開始未荒,找到其中一個任務隊列執(zhí)行完畢专挪,然后再執(zhí)行所有的micro-task,這樣一直循環(huán)下去茄猫。

其中每一個任務的執(zhí)行狈蚤,無論是macro-task還是micro-task,都是借助函數(shù)調用棧來完成划纽。

// 為了方便理解脆侮,我以打印出來的字符作為當前的任務名稱
setTimeout(function() {
    console.log('timeout1');
})

new Promise(function(resolve) {
    console.log('promise1');
    for(var i = 0; i < 1000; i++) {
        i == 99 && resolve();
    }
    console.log('promise2');
}).then(function() {
    console.log('then1');
})

console.log('global1');


//////////////
promise1
promise2
global1
then1
timeout1

首先,事件循環(huán)從宏任務隊列開始勇劣,這個時候靖避,宏任務隊列中,只有一個script(整體代碼)任務比默。每一個任務的執(zhí)行順序幻捏,都依靠函數(shù)調用棧來搞定,而當遇到任務源時命咐,則會先分發(fā)任務到對應的隊列中去篡九,所以,上面例子的第一步執(zhí)行如下圖所示醋奠。


首先script任務開始執(zhí)行榛臼,全局上下文入棧
首先script任務開始執(zhí)行伊佃,全局上下文入棧

第二步:script任務執(zhí)行時首先遇到了setTimeout,setTimeout為一個宏任務源沛善,那么他的作用就是將任務分發(fā)到它對應的隊列中

宏任務timeout1進入setTimeout隊列
宏任務timeout1進入setTimeout隊列

第三步:script執(zhí)行時遇到Promise實例航揉。Promise構造函數(shù)中的第一個參數(shù),是在new的時候執(zhí)行金刁,因此不會進入任何其他的隊列帅涂,而是直接在當前任務直接執(zhí)行了,而后續(xù)的.then則會被分發(fā)到micro-task的Promise隊列中去尤蛮。

因此媳友,構造函數(shù)執(zhí)行時,里面的參數(shù)進入函數(shù)調用棧執(zhí)行抵屿。for循環(huán)不會進入任何隊列庆锦,因此代碼會依次執(zhí)行,所以這里的promise1和promise2會依次輸出

promise1入棧執(zhí)行轧葛,這時promise1被最先輸出
promise1入棧執(zhí)行搂抒,這時promise1被最先輸出
resolve在for循環(huán)中入棧執(zhí)行
resolve在for循環(huán)中入棧執(zhí)行
構造函數(shù)執(zhí)行完畢的過程中,resolve執(zhí)行完畢出棧尿扯,promise2輸出求晶,promise1也出棧,then執(zhí)行時衷笋,Promise任務then1進入對應隊列
構造函數(shù)執(zhí)行完畢的過程中芳杏,resolve執(zhí)行完畢出棧,promise2輸出辟宗,promise1也出棧爵赵,then執(zhí)行時,Promise任務then1進入對應隊列

script任務繼續(xù)往下執(zhí)行泊脐,最后只有一句輸出了globa1空幻,然后,全局任務就執(zhí)行完畢了容客。

第四步:第一個宏任務script執(zhí)行完畢之后秕铛,就開始執(zhí)行所有的可執(zhí)行的微任務。這個時候缩挑,微任務中但两,只有Promise隊列中的一個任務then1,因此直接執(zhí)行就行了供置,執(zhí)行結果輸出then1谨湘,當然,他的執(zhí)行,也是進入函數(shù)調用棧中執(zhí)行的悲关。

執(zhí)行所有的微任務
執(zhí)行所有的微任務

第五步:當所有的micro-tast執(zhí)行完畢之后谎僻,表示第一輪的循環(huán)就結束了。這個時候就得開始第二輪的循環(huán)寓辱。第二輪循環(huán)仍然從宏任務macro-task開始

微任務被清空

這個時候,我們發(fā)現(xiàn)宏任務中赤拒,只有在setTimeout隊列中還要一個timeout1的任務等待執(zhí)行秫筏。因此就直接執(zhí)行即可

timeout1入棧執(zhí)行
timeout1入棧執(zhí)行

這個時候宏任務隊列與微任務隊列中都沒有任務了,所以代碼就不會再輸出其他東西了挎挖。

那么上面這個例子的輸出結果就顯而易見这敬。大家可以自行嘗試體會。

這個例子比較簡答蕉朵,涉及到的隊列任務并不多崔涂,因此讀懂了它還不能全面的了解到事件循環(huán)機制的全貌。所以我下面弄了一個復雜一點的例子始衅,再給大家解析一番冷蚂,相信讀懂之后,事件循環(huán)這個問題汛闸,再面試中再次被問到就難不倒大家了

console.log('golb1');

setTimeout(function() {
    console.log('timeout1');
    process.nextTick(function() {
        console.log('timeout1_nextTick');
    })
    new Promise(function(resolve) {
        console.log('timeout1_promise');
        resolve();
    }).then(function() {
        console.log('timeout1_then')
    })
})

setImmediate(function() {
    console.log('immediate1');
    process.nextTick(function() {
        console.log('immediate1_nextTick');
    })
    new Promise(function(resolve) {
        console.log('immediate1_promise');
        resolve();
    }).then(function() {
        console.log('immediate1_then')
    })
})

process.nextTick(function() {
    console.log('glob1_nextTick');
})
new Promise(function(resolve) {
    console.log('glob1_promise');
    resolve();
}).then(function() {
    console.log('glob1_then')
})

setTimeout(function() {
    console.log('timeout2');
    process.nextTick(function() {
        console.log('timeout2_nextTick');
    })
    new Promise(function(resolve) {
        console.log('timeout2_promise');
        resolve();
    }).then(function() {
        console.log('timeout2_then')
    })
})

process.nextTick(function() {
    console.log('glob2_nextTick');
})
new Promise(function(resolve) {
    console.log('glob2_promise');
    resolve();
}).then(function() {
    console.log('glob2_then')
})

setImmediate(function() {
    console.log('immediate2');
    process.nextTick(function() {
        console.log('immediate2_nextTick');
    })
    new Promise(function(resolve) {
        console.log('immediate2_promise');
        resolve();
    }).then(function() {
        console.log('immediate2_then')
    })
})

第一步:宏任務script首先執(zhí)行蝙茶。全局入棧。glob1輸出诸老。

script首先執(zhí)行
script首先執(zhí)行

第二步隆夯,執(zhí)行過程遇到setTimeout。setTimeout作為任務分發(fā)器别伏,將任務分發(fā)到對應的宏任務隊列中

timeout1進入對應隊列
timeout1進入對應隊列

第三步:執(zhí)行過程遇到setImmediate蹄衷。setImmediate也是一個宏任務分發(fā)器,將任務分發(fā)到對應的任務隊列中厘肮。setImmediate的任務隊列會在setTimeout隊列的后面執(zhí)行

setImmediate(function() {
    console.log('immediate1');
    process.nextTick(function() {
        console.log('immediate1_nextTick');
    })
    new Promise(function(resolve) {
        console.log('immediate1_promise');
        resolve();
    }).then(function() {
        console.log('immediate1_then')
    })
})
進入setImmediate隊列
進入setImmediate隊列

第四步:執(zhí)行遇到nextTick愧口,process.nextTick是一個微任務分發(fā)器,它會將任務分發(fā)到對應的微任務隊列中去

process.nextTick(function() {
    console.log('glob1_nextTick');
})
nextTick
nextTick

第五步:執(zhí)行遇到Promise轴脐。Promise的then方法會將任務分發(fā)到對應的微任務隊列中调卑,但是它構造函數(shù)中的方法會直接執(zhí)行。因此大咱,glob1_promise會第二個輸出恬涧。

new Promise(function(resolve) {
    console.log('glob1_promise');
    resolve();
}).then(function() {
    console.log('glob1_then')
})

先是函數(shù)調用棧的變化
先是函數(shù)調用棧的變化
然后glob1_then任務進入隊列
然后glob1_then任務進入隊列

第六步:執(zhí)行遇到第二個setTimeout。

setTimeout(function() {
    console.log('timeout2');
    process.nextTick(function() {
        console.log('timeout2_nextTick');
    })
    new Promise(function(resolve) {
        console.log('timeout2_promise');
        resolve();
    }).then(function() {
        console.log('timeout2_then')
    })
})
timeout2進入對應隊列
timeout2進入對應隊列

第七步:先后遇到nextTick與Promise

process.nextTick(function() {
    console.log('glob2_nextTick');
})
new Promise(function(resolve) {
    console.log('glob2_promise');
    resolve();
}).then(function() {
    console.log('glob2_then')
})

glob2_nextTick與Promise任務分別進入各自的隊列
glob2_nextTick與Promise任務分別進入各自的隊列

第八步:再次遇到setImmediate碴巾。

setImmediate(function() {
    console.log('immediate2');
    process.nextTick(function() {
        console.log('immediate2_nextTick');
    })
    new Promise(function(resolve) {
        console.log('immediate2_promise');
        resolve();
    }).then(function() {
        console.log('immediate2_then')
    })
})

nextTick
nextTick

這個時候溯捆,script中的代碼就執(zhí)行完畢了,執(zhí)行過程中,遇到不同的任務分發(fā)器提揍,就將任務分發(fā)到各自對應的隊列中去啤月。接下來,將會執(zhí)行所有的微任務隊列中的任務劳跃。

其中谎仲,nextTick隊列會比Promie先執(zhí)行。nextTick中的可執(zhí)行任務執(zhí)行完畢之后刨仑,才會開始執(zhí)行Promise隊列中的任務郑诺。

當所有可執(zhí)行的微任務執(zhí)行完畢之后,這一輪循環(huán)就表示結束了杉武。下一輪循環(huán)繼續(xù)從宏任務隊列開始執(zhí)行辙诞。

這個時候,script已經執(zhí)行完畢轻抱,所以就從setTimeout隊列開始執(zhí)行飞涂。

第二輪循環(huán)初始狀態(tài)
第二輪循環(huán)初始狀態(tài)

setTimeout任務的執(zhí)行,也依然是借助函數(shù)調用棧來完成祈搜,并且遇到任務分發(fā)器的時候也會將任務分發(fā)到對應的隊列中去较店。

只有當setTimeout中所有的任務執(zhí)行完畢之后,才會再次開始執(zhí)行微任務隊列夭问。并且清空所有的可執(zhí)行微任務泽西。

setTiemout隊列產生的微任務執(zhí)行完畢之后,循環(huán)則回過頭來開始執(zhí)行setImmediate隊列缰趋。仍然是先將setImmediate隊列中的任務執(zhí)行完畢,再執(zhí)行所產生的微任務秘血。

當setImmediate隊列執(zhí)行產生的微任務全部執(zhí)行之后味抖,第二輪循環(huán)也就結束了

// 用數(shù)組模擬一個隊列
var tasks = [];

// 模擬一個事件分發(fā)器
var addFn1 = function(task) {
    tasks.push(task);
}

// 執(zhí)行所有的任務
var flush = function() {
    tasks.map(function(task) {
        task();
    })
}

// 最后利用setTimeout/或者其他你認為合適的方式丟入事件循環(huán)中
setTimeout(function() {
    flush();
})

// 當然,也可以不用丟進事件循環(huán)灰粮,而是我們自己手動在適當?shù)臅r機去執(zhí)行對應的某一個方法

var dispatch = function(name) {
    tasks.map(function(item) {
        if(item.name == name) {
            item.handler();
        }
    })
}

// 當然仔涩,我們把任務丟進去的時候,多保存一個name即可粘舟。
// 這時候熔脂,task的格式就如下
demoTask =  {
    name: 'demo',
    handler: function() {}
}

// 于是,一個訂閱-通知的設計模式就這樣輕松的被實現(xiàn)了

最終結果:

golb1
glob1_promise
glob2_promise
glob1_nextTick
glob2_nextTick
glob1_then
glob2_then
timeout1
timeout1_promise
timeout2
timeout2_promise
timeout1_nextTick
timeout2_nextTick
timeout1_then
timeout2_then
immediate1
immediate1_promise
immediate2
immediate2_promise
immediate1_nextTick
immediate2_nextTick
immediate1_then
immediate2_then

總結一下:雖然宏任務總是比微任務先執(zhí)行,但是我們往往會忽略script這個宏任務,所以實際上最開始的宏任務會比微任務后執(zhí)行痹栖,promise構造函數(shù)里的函數(shù)不會在隊列中温峭,而是直接執(zhí)行迹鹅,他跟隨promise這個任務分發(fā)器一樣會立即執(zhí)行,而then中的函數(shù)會進入微任務隊列

這個前端面試在搞事

80% 應聘者都不及格的 JS 面試題

回到正題茴迁,那么什么是異步呢夸盟,眾所周知秽荞,js是單線程的語言骤公,腦袋一根筋,對于拿到的程序扬跋,一行一行的執(zhí)行阶捆,上面的執(zhí)行為完成,就傻傻的等著

var i, t = Date.now()
for (i = 0; i < 100000000; i++) {
}
console.log(Date.now() - t)  // 274(chrome瀏覽器)

執(zhí)行程序這樣沒有問題钦听,但是對于 JS 最初使用的環(huán)境 ———— 瀏覽器客戶端 ———— 就不一樣了趁猴。因此在瀏覽器端運行的 js ,可能會有大量的網(wǎng)絡請求彪见,而一個網(wǎng)絡資源啥時候返回,這個時間是不可預估的娱挨。這種情況也要傻傻的等著余指、卡頓著、啥都不做嗎跷坝?———— 那肯定不行酵镜。

因此,JS 對于這種場景就設計了異步 ———— 即柴钻,發(fā)起一個網(wǎng)絡請求淮韭,就先不管這邊了,先干其他事兒贴届,網(wǎng)絡請求啥時候返回結果靠粪,到時候再說。這樣就能保證一個網(wǎng)頁的流程運行

同步:一件事接著一件事做毫蚓,上一件沒做完占键,下一件事也不做,連續(xù)的執(zhí)行
異步:就是一個任務分成兩段元潘,先執(zhí)行第一段畔乙,然后轉而執(zhí)行其他任務,等做好了準備翩概,再回過頭執(zhí)行第二段牲距,不連續(xù)的執(zhí)行

JavaScript 語言對異步編程的實現(xiàn),就是回調函數(shù)钥庇。所謂回調函數(shù)牍鞠,就是把任務的第二段單獨寫在一個函數(shù)里面,等到重新執(zhí)行這個任務的時候上沐,就直接調用這個函數(shù)

f1(f2)

回調函數(shù)就是將函數(shù)f2當做參數(shù)傳給f1皮服,f1執(zhí)行之后才執(zhí)行f2,f2就是f1的回調函數(shù),所有的異步原理都是基于回調函數(shù)龄广,無論是promise還是async硫眯,他們都只是回調函數(shù)的語法糖。(回調函數(shù)|事件監(jiān)聽|發(fā)布/訂閱|Promise)

事件綁定與異步操作原理相似择同,但是它與異步有兩個不同的地方

1.event-loop 執(zhí)行時两入,調用的源不一樣。異步操作是系統(tǒng)自動調用敲才,無論是setTimeout時間到了還是$.ajax請求返回了裹纳,系統(tǒng)會自動調用。而事件綁定就需要用戶手動觸發(fā)

2.從設計上來將紧武,事件綁定有著明顯的“訂閱-發(fā)布”的設計模式剃氧,而異步操作卻沒有

開發(fā)中比較常用的異步操作有:

網(wǎng)絡請求,如ajax http.get
IO 操作阻星,如readFile readdir
定時函數(shù)朋鞍,如setTimeout setInterval

promise:
先把規(guī)范理解一下,再來講講它的api妥箕,最后實現(xiàn)一個promise來徹底理解滥酥。

  • Promise 本質是一個狀態(tài)機。每個 promise 只能是 3 種狀態(tài)中的一種:pending畦幢、fulfilled 或 rejected坎吻。狀態(tài)轉變只能是 pending -> fulfilled 或者 pending -> rejected。狀態(tài)轉變不可逆宇葱。

  • then 方法可以被同一個 promise 調用多次瘦真,then方法返回一個新的Promise

  • then 方法必須返回一個 promise。規(guī)范里沒有明確說明返回一個新的 promise 還是復用老的 promise(即 return this)贝搁,大多數(shù)實現(xiàn)都是返回一個新的 promise吗氏,而且復用老的 promise 可能改變內部狀態(tài),這與規(guī)范也是相違背的雷逆。

  • 值穿透

  • 只有一個then方法弦讽,沒有catch,race膀哲,all等方法往产,甚至沒有構造函數(shù)

Promise標準中僅指定了Promise對象的then方法的行為,其它一切我們常見的方法/函數(shù)都并沒有指定某宪,包括catch仿村,race,all等常用方法兴喂,甚至也沒有指定該如何構造出一個Promise對象

  • 不同Promise的實現(xiàn)需要可以相互調用

Promise的構造函數(shù)接收一個參數(shù)蔼囊,是函數(shù)焚志,并且傳入兩個參數(shù):resolve,reject畏鼓,分別表示異步操作執(zhí)行成功后的回調函數(shù)和異步操作執(zhí)行失敗后的回調函數(shù)酱酬。其實這里用“成功”和“失敗”來描述并不準確,按照標準來講云矫,resolve是將Promise的狀態(tài)置為fullfiled膳沽,reject是將Promise的狀態(tài)置為rejected

var p = new Promise(function(resolve, reject){
    //做一些異步操作
    setTimeout(function(){
        console.log('執(zhí)行完成');
        resolve('隨便什么數(shù)據(jù)');
    }, 2000);
});

//執(zhí)行完成

運行代碼,會在2秒后輸出“執(zhí)行完成”让禀。注意挑社!我只是new了一個對象,并沒有調用它巡揍,我們傳進去的函數(shù)就已經執(zhí)行了痛阻,這是需要注意的一個細節(jié)。所以我們用Promise的時候一般是包在一個函數(shù)中腮敌,在需要的時候去運行這個函數(shù)

function runAsync(){
    var p = new Promise(function(resolve, reject){
        //做一些異步操作
        setTimeout(function(){
            console.log('執(zhí)行完成');
            resolve('隨便什么數(shù)據(jù)');
        }, 2000);
    });
    return p;            
}
runAsync()

在我們包裝好的函數(shù)最后录平,會return出Promise對象,也就是說缀皱,執(zhí)行這個函數(shù)我們得到了一個Promise對象。還記得Promise對象上有then动猬、catch方法吧啤斗?這就是強大之處了

function runAsync(){
    var p = new Promise(function(resolve, reject){
        //做一些異步操作
        setTimeout(function(){
            console.log('執(zhí)行完成');
            resolve('隨便什么數(shù)據(jù)');
        }, 2000);
    });
    return p;            
}

runAsync().then(function(data){
    console.log(data);
    //后面可以用傳過來的數(shù)據(jù)做些其他操作
    //......
});

//執(zhí)行完成
//隨便什么數(shù)據(jù)

在runAsync()的返回上直接調用then方法,then接收一個參數(shù)赁咙,是函數(shù)钮莲,并且會拿到我們在runAsync中調用resolve時傳的的參數(shù)。運行這段代碼彼水,會在2秒后輸出“執(zhí)行完成”崔拥,緊接著輸出“隨便什么數(shù)據(jù)

這時候你應該有所領悟了,原來then里面的函數(shù)就跟我們平時的回調函數(shù)一個意思凤覆,能夠在runAsync這個異步任務執(zhí)行完成之后被執(zhí)行链瓦。這就是Promise的作用了,簡單來講盯桦,就是能把原來的回調寫法分離出來慈俯,在異步操作執(zhí)行完后,用鏈式調用的方式執(zhí)行回調函數(shù)

從表面上看拥峦,Promise只是能夠簡化層層回調的寫法贴膘,而實質上,Promise的精髓是“狀態(tài)”略号,用維護狀態(tài)刑峡、傳遞狀態(tài)的方式來使得回調函數(shù)能夠及時調用洋闽,它比傳遞callback函數(shù)要簡單、靈活的多突梦。所以使用Promise的正確場景是這樣的:

function runAsync1(){
    var p = new Promise(function(resolve, reject){
        //做一些異步操作
        setTimeout(function(){
            console.log('異步任務1執(zhí)行完成');
            resolve('隨便什么數(shù)據(jù)1');
        }, 1000);
    });
    return p;            
}
function runAsync2(){
    var p = new Promise(function(resolve, reject){
        //做一些異步操作
        setTimeout(function(){
            console.log('異步任務2執(zhí)行完成');
            resolve('隨便什么數(shù)據(jù)2');
        }, 2000);
    });
    return p;            
}
function runAsync3(){
    var p = new Promise(function(resolve, reject){
        //做一些異步操作
        setTimeout(function(){
            console.log('異步任務3執(zhí)行完成');
            resolve('隨便什么數(shù)據(jù)3');
        }, 2000);
    });
    return p;            
}

runAsync1()
.then(function(data){
    console.log(data);
    return runAsync2();
})
.then(function(data){
    console.log(data);
    return runAsync3();
})
.then(function(data){
    console.log(data);
});

//異步任務1執(zhí)行完成
//隨便什么數(shù)據(jù)1
//異步任務2執(zhí)行完成
//隨便什么數(shù)據(jù)2
//異步任務3執(zhí)行完成
//隨便什么數(shù)據(jù)3

我們光用了resolve诫舅,還沒用reject呢,它是做什么的呢阳似?事實上骚勘,我們前面的例子都是只有“執(zhí)行成功”的回調,還沒有“失敗”的情況撮奏,reject的作用就是把Promise的狀態(tài)置為rejected俏讹,這樣我們在then中就能捕捉到,然后執(zhí)行“失敗”情況的回調

function getNumber(){
    var p = new Promise(function(resolve, reject){
        //做一些異步操作
        setTimeout(function(){
            var num = Math.ceil(Math.random()*10); //生成1-10的隨機數(shù)
            if(num<=5){
                resolve(num);
            }
            else{
                reject('數(shù)字太大了');
            }
        }, 2000);
    });
    return p;            
}

getNumber()
.then(
    function(data){
        console.log('resolved');
        console.log(data);
    }, 
    function(reason, data){
        console.log('rejected');
        console.log(reason);
    }
);

getNumber函數(shù)用來異步獲取一個數(shù)字畜吊,2秒后執(zhí)行完成泽疆,如果數(shù)字小于等于5,我們認為是“成功”了玲献,調用resolve修改Promise的狀態(tài)殉疼。否則我們認為是“失敗”了,調用reject并傳遞一個參數(shù)捌年,作為失敗的原因瓢娜。

運行getNumber并且在then中傳了兩個參數(shù),then方法可以接受兩個參數(shù)礼预,第一個對應resolve的回調眠砾,第二個對應reject的回調。所以我們能夠分別拿到他們傳過來的數(shù)據(jù)托酸。多次運行這段代碼褒颈,你會隨機得到下面兩種結果

resolver 2
||
rejected 太大了

我們知道Promise對象除了then方法,還有一個catch方法励堡,它是做什么用的呢谷丸?其實它和then的第二個參數(shù)一樣,用來指定reject的回調应结,用法是這樣:

getNumber()
.then(function(data){
    console.log('resolved');
    console.log(data);
})
.catch(function(reason){
    console.log('rejected');
    console.log(reason);
});

效果和寫在then的第二個參數(shù)里面一樣,相當于then(null,fn)刨疼。不過它還有另外一個作用:在執(zhí)行resolve的回調(也就是上面then中的第一個參數(shù))時,如果拋出異常了(代碼出錯了)鹅龄,那么并不會報錯卡死js币狠,而是會進到這個catch方法中,所以我們要多用catch少用rejected

getNumber()
.then(function(data){
    console.log('resolved');
    console.log(data);
    console.log(somedata); //此處的somedata未定義
})
.catch(function(reason){
    console.log('rejected');
    console.log(reason);
});

在resolve的回調中,我們console.log(somedata);而somedata這個變量是沒有被定義的砾层。如果我們不用Promise漩绵,代碼運行到這里就直接在控制臺報錯了,不往下運行了肛炮。但是在這里止吐,會得到這樣的結果:

resolved
4
rejected
somedata is not defined

也就是說進到catch方法里面去了宝踪,而且把錯誤原因傳到了reason參數(shù)中。即便是有錯誤的代碼也不會報錯了碍扔,這與我們的try/catch語句有相同的功能

Promise的all方法提供了并行執(zhí)行異步操作的能力瘩燥,并且在所有異步操作執(zhí)行完后才執(zhí)行回調。我們仍舊使用上面定義好的runAsync1不同、runAsync2厉膀、runAsync3這三個函數(shù),看下面的例子:

Promise
.all([runAsync1(), runAsync2(), runAsync3()])
.then(function(results){
    console.log(results);
});

用Promise.all來執(zhí)行二拐,all接收一個數(shù)組參數(shù)服鹅,里面的值最終都算返回Promise對象。這樣百新,三個異步操作的并行執(zhí)行的企软,等到它們都執(zhí)行完后才會進到then里面。那么饭望,三個異步操作返回的數(shù)據(jù)哪里去了呢仗哨?都在then里面呢,all會把所有異步操作的結果放進一個數(shù)組中傳給then铅辞,就是上面的results厌漂。所以上面代碼的輸出結果就是:

//異步任務1執(zhí)行完成
//異步任務2執(zhí)行完成
//異步任務3執(zhí)行完成
//[隨便什么數(shù)據(jù)1,隨便什么數(shù)據(jù)2,隨便什么數(shù)據(jù)3]

有了all,你就可以并行執(zhí)行多個異步操作斟珊,并且在一個回調中處理所有的返回數(shù)據(jù)桩卵,是不是很酷?有一個場景是很適合用這個的倍宾,一些游戲類的素材比較多的應用,打開網(wǎng)頁時胜嗓,預先加載需要用到的各種資源如圖片高职、flash以及各種靜態(tài)文件。所有的都加載完后辞州,我們再進行頁面的初始化

ll方法的效果實際上是「誰跑的慢怔锌,以誰為準執(zhí)行回調」,那么相對的就有另一個方法「誰跑的快变过,以誰為準執(zhí)行回調」埃元,這就是race方法,這個詞本來就是賽跑的意思媚狰。race的用法與all一樣岛杀,我們把上面runAsync1的延時改為1秒來看一下

Promise
.race([runAsync1(), runAsync2(), runAsync3()])
.then(function(results){
    console.log(results);
});

這三個異步操作同樣是并行執(zhí)行的。結果你應該可以猜到崭孤,1秒后runAsync1已經執(zhí)行完了类嗤,此時then里面的就執(zhí)行了糊肠。結果是這樣的

//異步任務1執(zhí)行完成
//隨便什么數(shù)據(jù)1
//異步任務2執(zhí)行完成
//異步任務3執(zhí)行完成

在then里面的回調開始執(zhí)行時,runAsync2()和runAsync3()并沒有停止遗锣,仍舊再執(zhí)行货裹。于是再過1秒后,輸出了他們結束的標志精偿。

這個race有什么用呢弧圆?使用場景還是很多的,比如我們可以用race給某個異步請求設置超時時間笔咽,并且在超時后執(zhí)行相應的操作搔预,代碼如下:

function requestImg(){
    var p = new Promise(function(resolve, reject){
        var img = new Image();
        img.onload = function(){
            resolve(img);
        }
        img.src = 'xxxxxx';
    });
    return p;
}

//延時函數(shù),用于給請求計時
function timeout(){
    var p = new Promise(function(resolve, reject){
        setTimeout(function(){
            reject('圖片請求超時');
        }, 5000);
    });
    return p;
}

Promise
.race([requestImg(), timeout()])
.then(function(results){
    console.log(results);
})
.catch(function(reason){
    console.log(reason);
});

requestImg函數(shù)會異步請求一張圖片拓轻,我把地址寫為"xxxxxx"斯撮,所以肯定是無法成功請求到的。timeout函數(shù)是一個延時5秒的異步操作扶叉。我們把這兩個返回Promise對象的函數(shù)放進race勿锅,于是他倆就會賽跑,如果5秒之內圖片請求成功了枣氧,那么遍進入then方法溢十,執(zhí)行正常的流程。如果5秒鐘圖片還未成功返回达吞,那么timeout就跑贏了张弛,則進入catch,報出“圖片請求超時”的信息

搞懂了這些就基本理解了promise酪劫,以下八句話更能理解到promsie的精髓:

  1. Promise的立即執(zhí)行性
var p = new Promise(function(resolve, reject){
  console.log("create a promise");
  resolve("success");
});

console.log("after new Promise");

p.then(function(value){
  console.log(value);
});
// create a promise
// after new Promise
// value

Promise對象表示未來某個將要發(fā)生的事件吞鸭,但在創(chuàng)建(new)Promise時,作為Promise參數(shù)傳入的函數(shù)是會被立即執(zhí)行的覆糟,只是其中執(zhí)行的代碼可以是異步代碼刻剥。有些同學會認為,當Promise對象調用then方法時滩字,Promise接收的函數(shù)才會執(zhí)行造虏,這是錯誤的。因此麦箍,代碼中"create a promise"先于"after new Promise"輸出

2.Promise 三種狀態(tài)

var p1 = new Promise(function(resolve,reject){
  resolve(1);
});
//p1 的函數(shù)參數(shù)中執(zhí)行的是一段同步代碼漓藕,Promise剛創(chuàng)建完成,resolve方法就已經被調用了挟裂,因而緊跟著的輸出顯示p1是resolved狀態(tài)

var p2 = new Promise(function(resolve,reject){
  setTimeout(function(){
    resolve(2);  
  }, 500);      
});
var p3 = new Promise(function(resolve,reject){
  setTimeout(function(){
    reject(3);  
  }, 500);      
});

console.log(p1);
console.log(p2);
console.log(p3);
setTimeout(function(){
  console.log(p2);
}, 1000);
setTimeout(function(){
  console.log(p3);
}, 1000);

p1.then(function(value){
  console.log(value);
});
p2.then(function(value){
  console.log(value);
});
p3.catch(function(err){
  console.log(err);
});

Promise {[[PromiseStatus]]: "resolved", [[PromiseValue]]: 1}
Promise {[[PromiseStatus]]: "pending", [[PromiseValue]]: undefined}
Promise {[[PromiseStatus]]: "pending", [[PromiseValue]]: undefined}
1
2
3
Promise {[[PromiseStatus]]: "resolved", [[PromiseValue]]: 2}
Promise {[[PromiseStatus]]: "rejected", [[PromiseValue]]: 3}

Promise的內部實現(xiàn)是一個狀態(tài)機享钞。Promise有三種狀態(tài):pending,resolved诀蓉,rejected嫩与。當Promise剛創(chuàng)建完成時寝姿,處于pending狀態(tài);當Promise中的函數(shù)參數(shù)執(zhí)行了resolve后划滋,Promise由pending狀態(tài)變成resolved狀態(tài)饵筑;如果在Promise的函數(shù)參數(shù)中執(zhí)行的不是resolve方法,而是reject方法处坪,那么Promise會由pending狀態(tài)變成rejected狀態(tài)根资。

p2、p3剛創(chuàng)建完成時同窘,控制臺輸出的這兩臺Promise都處于pending狀態(tài)玄帕,但為什么p1是resolved狀態(tài)呢? 這是因為p1 的函數(shù)參數(shù)中執(zhí)行的是一段同步代碼想邦,Promise剛創(chuàng)建完成裤纹,resolve方法就已經被調用了,因而緊跟著的輸出顯示p1是resolved狀態(tài)丧没。我們通過兩個setTimeout函數(shù)鹰椒,延遲1s后再次輸出p2、p3的狀態(tài)呕童,此時p2漆际、p3已經執(zhí)行完成,狀態(tài)分別變成resolved和rejected

分別分析就看得很清楚了

var p1 = new Promise(function(resolve,reject){
  resolve(1);
});
console.log(p1);
p1.then(function(value){
  console.log(value);
});
//
Promise {[[PromiseStatus]]: "resolved", [[PromiseValue]]: 1}
1
Promise {[[PromiseStatus]]: "resolved", [[PromiseValue]]: undefined}
var p2 = new Promise(function(resolve,reject){
  setTimeout(function(){
    resolve(2);  
  }, 500);      
});
console.log(p2);
setTimeout(function(){
  console.log(p2);
}, 1000);
p2.then(function(value){
  console.log(value);
});

//
Promise {[[PromiseStatus]]: "pending", [[PromiseValue]]: undefined}
Promise {[[PromiseStatus]]: "pending", [[PromiseValue]]: undefined}
2
Promise {[[PromiseStatus]]: "resolved", [[PromiseValue]]: 2}
var p3 = new Promise(function(resolve,reject){
  setTimeout(function(){
    reject(3);  
  }, 500);      
});
console.log(p3);
setTimeout(function(){
  console.log(p3);
}, 1000);
p3.then(function(value){
  console.log(value);
});

Promise {[[PromiseStatus]]: "pending", [[PromiseValue]]: undefined}
Promise {[[PromiseStatus]]: "pending", [[PromiseValue]]: undefined}
3
Promise {[[PromiseStatus]]: "rejected", [[PromiseValue]]: 2}
  1. Promise 狀態(tài)的不可逆性
var p1 = new Promise(function(resolve, reject){
  resolve("success1");
  resolve("success2");
});

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

p1.then(function(value){
  console.log(value);
});

p2.then(function(value){
  console.log(value);
});

"success1"
"success"

Promise狀態(tài)的一旦變成resolved或rejected時夺饲,Promise的狀態(tài)和值就固定下來了奸汇,不論你后續(xù)再怎么調用resolve或reject方法,都不能改變它的狀態(tài)和值往声。因此擂找,p1中resolve("success2")并不能將p1的值更改為success2,p2中reject("reject")也不能將p2的狀態(tài)由resolved改變?yōu)閞ejected.

  1. 鏈式調用
var p = new Promise(function(resolve, reject){
  resolve(1);
});
p.then(function(value){               //第一個then
  console.log(value);
  return value*2;
}).then(function(value){              //第二個then
  console.log(value);
}).then(function(value){              //第三個then
  console.log(value);
  return Promise.resolve('resolve'); 
}).then(function(value){              //第四個then
  console.log(value);
  return Promise.reject('reject');
}).then(function(value){              //第五個then
  console.log('resolve: '+ value);
}, function(err){
  console.log('reject: ' + err);
})
1
2
undefined
"resolve"
"reject: reject"

Promise對象的then方法返回一個新的Promise對象浩销,因此可以通過鏈式調用then方法贯涎。then方法接收兩個函數(shù)作為參數(shù),第一個參數(shù)是Promise執(zhí)行成功時的回調撼嗓,第二個參數(shù)是Promise執(zhí)行失敗時的回調。兩個函數(shù)只會有一個被調用欢唾,函數(shù)的返回值將被用作創(chuàng)建then返回的Promise對象且警。這兩個參數(shù)的返回值可以是以下三種情況中的一種:

  • return 一個同步的值 ,或者 undefined(當沒有返回一個有效值時礁遣,默認返回undefined)斑芜,then方法將返回一個resolved狀態(tài)的Promise對象,Promise對象的值就是這個返回值祟霍。

  • return 另一個 Promise杏头,then方法將根據(jù)這個Promise的狀態(tài)和值創(chuàng)建一個新的Promise對象返回盈包。

  • throw 一個同步異常,then方法將返回一個rejected狀態(tài)的Promise, 值是該異常

根據(jù)以上分析醇王,代碼中第一個then會返回一個值為2(1*2)呢燥,狀態(tài)為resolved的Promise對象,于是第二個then輸出的值是2寓娩。第二個then中沒有返回值叛氨,因此將返回默認的undefined,于是在第三個then中輸出undefined棘伴。第三個then和第四個then中分別返回一個狀態(tài)是resolved的Promise和一個狀態(tài)是rejected的Promise寞埠,依次由第四個then中成功的回調函數(shù)和第五個then中失敗的回調函數(shù)處理

  1. Promise then() 回調異步性
var p = new Promise(function(resolve, reject){
  resolve("success");
});

p.then(function(value){
  console.log(value);
});

console.log("which one is called first ?");

"which one is called first ?"
"success"

Promise接收的函數(shù)參數(shù)是同步執(zhí)行的,但then方法中的回調函數(shù)執(zhí)行則是異步的始腾,因此材失,"success"會在后面輸出

6.Promise 中的異常

var p1 = new Promise( function(resolve,reject){
  foo.bar();
  resolve( 1 );      
});

p1.then(
  function(value){
    console.log('p1 then value: ' + value);
  },
  function(err){
    console.log('p1 then err: ' + err);
  }
).then(
  function(value){
    console.log('p1 then then value: '+value);
  },
  function(err){
    console.log('p1 then then err: ' + err);
  }
);

var p2 = new Promise(function(resolve,reject){
  resolve( 2 );    
});

p2.then(
  function(value){
    console.log('p2 then value: ' + value);
    foo.bar();
  }, 
  function(err){
    console.log('p2 then err: ' + err);
  }
).then(
  function(value){
    console.log('p2 then then value: ' + value);
  },
  function(err){
    console.log('p2 then then err: ' + err);
    return 1;
  }
).then(
  function(value){
    console.log('p2 then then then value: ' + value);
  },
  function(err){
    console.log('p2 then then then err: ' + err);
  }
);

///
p1 then err: ReferenceError: foo is not defined
p2 then value: 2
p1 then then value: undefined
p2 then then err: ReferenceError: foo is not defined
p2 then then then value: 1

Promise中的異常由then參數(shù)中第二個回調函數(shù)(Promise執(zhí)行失敗的回調)處理镰矿,異常信息將作為Promise的值。異常一旦得到處理饭冬,then返回的后續(xù)Promise對象將恢復正常,并會被Promise執(zhí)行成功的回調函數(shù)處理颇象。另外伍伤,需要注意p1、p2 多級then的回調函數(shù)是交替執(zhí)行的 遣钳,這正是由Promise then回調的異步性決定的

7.Promise.resolve()

var p1 = Promise.resolve( 1 );
var p2 = Promise.resolve( p1 );
var p3 = new Promise(function(resolve, reject){
  resolve(1);
});
var p4 = new Promise(function(resolve, reject){
  resolve(p1);
});

console.log(p1 === p2); 
console.log(p1 === p3);
console.log(p1 === p4);
console.log(p3 === p4);

p4.then(function(value){
  console.log('p4=' + value);
});

p2.then(function(value){
  console.log('p2=' + value);
})

p1.then(function(value){
  console.log('p1=' + value);
})

////
true
false
false
false
p2=1
p1=1
p4=1
  1. resolve vs reject
var p1 = new Promise(function(resolve, reject){
  resolve(Promise.resolve('resolve'));
});

var p2 = new Promise(function(resolve, reject){
  resolve(Promise.reject('reject'));
});

var p3 = new Promise(function(resolve, reject){
  reject(Promise.resolve('resolve'));
});

p1.then(
  function fulfilled(value){
    console.log('fulfilled: ' + value);
  }, 
  function rejected(err){
    console.log('rejected: ' + err);
  }
);

p2.then(
  function fulfilled(value){
    console.log('fulfilled: ' + value);
  }, 
  function rejected(err){
    console.log('rejected: ' + err);
  }
);

p3.then(
  function fulfilled(value){
    console.log('fulfilled: ' + value);
  }, 
  function rejected(err){
    console.log('rejected: ' + err);
  }
);

////
p3 rejected: [object Promise]
p1 fulfilled: resolve
p2 rejected: reject

拆分:

var p1 = new Promise(function(resolve, reject){
  resolve(Promise.resolve('resolve'));
});
p1.then(
  function fulfilled(value){
    console.log('fulfilled: ' + value);
  }, 
  function rejected(err){
    console.log('rejected: ' + err);
  }
);
fulfilled: resolve

Promise回調函數(shù)中的第一個參數(shù)resolve扰魂,會對Promise執(zhí)行"拆箱"動作。即當resolve的參數(shù)是一個Promise對象時蕴茴,resolve會"拆箱"獲取這個Promise對象的狀態(tài)和值劝评,但這個過程是異步的。p1"拆箱"后倦淀,獲取到Promise對象的狀態(tài)是resolved蒋畜,因此fulfilled回調被執(zhí)行

var p2 = new Promise(function(resolve, reject){
  resolve(Promise.reject('reject'));
});
p2.then(
  function fulfilled(value){
    console.log('fulfilled: ' + value);
  }, 
  function rejected(err){
    console.log('rejected: ' + err);
  }
);
//
rejected: reject
var p3 = new Promise(function(resolve, reject){
  reject(Promise.resolve('resolve'));
  //reject的參數(shù)會直接傳遞給then方法中的rejected回調
});
p3.then(
  function fulfilled(value){
    console.log('fulfilled: ' + value);
  }, 
  function rejected(err){
    console.log('rejected: ' + err);
  }
);
rejected: [object Promise]

p2"拆箱"后,獲取到Promise對象的狀態(tài)是rejected撞叽,因此rejected回調被執(zhí)行姻成。但Promise回調函數(shù)中的第二個參數(shù)reject不具備”拆箱“的能力,reject的參數(shù)會直接傳遞給then方法中的rejected回調愿棋。因此科展,即使p3 reject接收了一個resolved狀態(tài)的Promise,then方法中被調用的依然是rejected糠雨,并且參數(shù)就是reject接收到的Promise對象

promise面試題:紅燈三秒亮一次才睹,綠燈一秒亮一次,黃燈2秒亮一次;如何讓三個燈不斷交替重復亮燈琅攘?(用Promse實現(xiàn)) 三個亮燈函數(shù)已經存在: function red(){ console.log('red'); } function green(){ console.log('green'); } function yellow(){ console.log('yellow'); }

setTimeout相關的異步隊列會掛起直到主進程空閑垮庐。如果使用while無限循環(huán),主進程永遠不會空閑坞琴,setTimeout的函數(shù)永遠不會執(zhí)行哨查!

function red(){
    console.log('red');
}
function green(){
    console.log('green');
}
function yellow(){
    console.log('yellow');
}

var tic = function(timmer, cb){
    return new Promise(function(resolve, reject) {
        setTimeout(function() {
            cb();
            resolve();
        }, timmer);
    });
};

var d = new Promise(function(resolve, reject){resolve();});
var step = function(def) {
    def.then(function(){
        return tic(3000, red);
    }).then(function(){
        return tic(2000, green);
    }).then(function(){
        return tic(1000, yellow);
    }).then(function(){
        step(def);
    });
}

step(d);
var tic = function(timmer, str){
    return new Promise(function(resolve, reject) {
        setTimeout(function() {
            console.log(str);
            resolve(1);
        }, timmer);
    });
};


function *gen(){
    yield tic(3000, 'red');
    yield tic(1000, 'green');
    yield tic(2000, 'yellow');
}

var iterator = gen();
var step = function(gen, iterator){
    var s = iterator.next();
    if (s.done) {
        step(gen, gen());
    } else {
        s.value.then(function() {
            step(gen, iterator);
        });
    }
}

step(gen, iterator);

使用then方法添加回調函數(shù):

// 寫法一
doSomething().then(function () {
  return doSomethingElse();
});

// 寫法二
doSomething().then(function () {
  doSomethingElse();
});

// 寫法三
doSomething().then(doSomethingElse());

// 寫法四
doSomething().then(doSomethingElse);

為了便于講解,下面這四種寫法都再用then方法接一個回調函數(shù)finalHandler置济。寫法一的finalHandler回調函數(shù)的參數(shù)解恰,是doSomethingElse函數(shù)的運行結果

doSomething().then(function () {
  return doSomethingElse();
}).then(finalHandler);

寫法二的finalHandler回調函數(shù)的參數(shù)是undefined

doSomething().then(function () {
  doSomethingElse();
  return;
}).then(finalHandler);

寫法三的finalHandler回調函數(shù)的參數(shù),是doSomethingElse函數(shù)返回的回調函數(shù)的運行結果

doSomething().then(doSomethingElse())
.then(finalHandler);

寫法四與寫法一只有一個差別浙于,那就是doSomethingElse會接收到doSomething()返回的結果

doSomething().then(doSomethingElse)
.then(finalHandler);

實現(xiàn)一個promise

var Promise = (function() {
  function Promise(resolver) {
    if (typeof resolver !== 'function') {
      throw new TypeError('Promise resolver ' + resolver + ' is not a function')
    }
    if (!(this instanceof Promise)) return new Promise(resolver)

    var self = this
    self.callbacks = []
    self.status = 'pending'

    function resolve(value) {
      if (value instanceof Promise) {
        return value.then(resolve, reject)
      }
      setTimeout(function() {
        if (self.status !== 'pending') {
          return
        }
        self.status = 'resolved'
        self.data = value

        for (var i = 0; i < self.callbacks.length; i++) {
          self.callbacks[i].onResolved(value)
        }
      })
    }

    function reject(reason) {
      setTimeout(function(){
        if (self.status !== 'pending') {
          return
        }
        self.status = 'rejected'
        self.data = reason

        for (var i = 0; i < self.callbacks.length; i++) {
          self.callbacks[i].onRejected(reason)
        }
      })
    }

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

  function resolvePromise(promise, x, resolve, reject) {
    var then
    var thenCalledOrThrow = false

    if (promise === x) {
      return reject(new TypeError('Chaining cycle detected for promise!'))
    }

    if (x instanceof Promise) {
      if (x.status === 'pending') {
        x.then(function(v) {
          resolvePromise(promise, v, resolve, reject);
        }, reject);
      } else {
        x.then(resolve, reject)
      }
      return
    }

    if ((x !== null) && ((typeof x === 'object') || (typeof x === 'function'))) {
      try {
        then = x.then
        if (typeof then === 'function') {
          then.call(x, function rs(y) {
            if (thenCalledOrThrow) return
            thenCalledOrThrow = true
            return resolvePromise(promise, y, resolve, reject)
          }, function rj(r) {
            if (thenCalledOrThrow) return
            thenCalledOrThrow = true
            return reject(r)
          })
        } else {
          return resolve(x)
        }
      } catch(e) {
        if (thenCalledOrThrow) return
        thenCalledOrThrow = true
        return reject(e)
      }
    } else {
      return resolve(x)
    }
  }

  Promise.prototype.then = function(onResolved, onRejected) {
    onResolved = typeof onResolved === 'function' ? onResolved : function(v){return v}
    onRejected = typeof onRejected === 'function' ? onRejected : function(r){throw r}
    var self = this
    var promise2

    if (self.status === 'resolved') {
      return promise2 = new Promise(function(resolve, reject) {
        setTimeout(function() {
          try {
            var value = onResolved(self.data)
            resolvePromise(promise2, value, resolve, reject)
          } catch(e) {
            return reject(e)
          }
        })
      })
    }

    if (self.status === 'rejected') {
      return promise2 = new Promise(function(resolve, reject) {
        setTimeout(function() {
          try {
            var value = onRejected(self.data)
            resolvePromise(promise2, value, resolve, reject)
          } catch(e) {
            return reject(e)
          }
        })
      })
    }

    if (self.status === 'pending') {
      return promise2 = new Promise(function(resolve, reject) {
        self.callbacks.push({
          onResolved: function(value) {
            try {
              var value = onResolved(value)
              resolvePromise(promise2, value, resolve, reject)
            } catch(e) {
              return reject(e)
            }
          },
          onRejected: function(reason) {
            try {
              var value = onRejected(reason)
              resolvePromise(promise2, value, resolve, reject)
            } catch(e) {
              return reject(e)
            }
          }
        })
      })
    }
  }

  Promise.prototype.valueOf = function() {
    return this.data
  }

  Promise.prototype.catch = function(onRejected) {
    return this.then(null, onRejected)
  }

  Promise.prototype.finally = function(fn) {
    // 為什么這里可以呢护盈,因為所有的then調用是一起的,但是這個then里調用fn又異步了一次羞酗,所以它總是最后調用的腐宋。
    // 當然這里只能保證在已添加的函數(shù)里是最后一次,不過這也是必然檀轨。
    // 不過看起來比其它的實現(xiàn)要簡單以及容易理解的多胸竞。
    // 貌似對finally的行為沒有一個公認的定義,所以這個實現(xiàn)目前是跟Q保持一致参萄,會返回一個新的Promise而不是原來那個卫枝。
    return this.then(function(v){
      setTimeout(fn)
      return v
    }, function(r){
      setTimeout(fn)
      throw r
    })
  }

  Promise.prototype.spread = function(fn, onRejected) {
    return this.then(function(values) {
      return fn.apply(null, values)
    }, onRejected)
  }

  Promise.prototype.inject = function(fn, onRejected) {
    return this.then(function(v) {
      return fn.apply(null, fn.toString().match(/\((.*?)\)/)[1].split(',').map(function(key){
        return v[key];
      }))
    }, onRejected)
  }

  Promise.prototype.delay = function(duration) {
    return this.then(function(value) {
      return new Promise(function(resolve, reject) {
        setTimeout(function() {
          resolve(value)
        }, duration)
      })
    }, function(reason) {
      return new Promise(function(resolve, reject) {
        setTimeout(function() {
          reject(reason)
        }, duration)
      })
    })
  }

  Promise.all = function(promises) {
    return new Promise(function(resolve, reject) {
      var resolvedCounter = 0
      var promiseNum = promises.length
      var resolvedValues = new Array(promiseNum)
      for (var i = 0; i < promiseNum; i++) {
        (function(i) {
          Promise.resolve(promises[i]).then(function(value) {
            resolvedCounter++
            resolvedValues[i] = value
            if (resolvedCounter == promiseNum) {
              return resolve(resolvedValues)
            }
          }, function(reason) {
            return reject(reason)
          })
        })(i)
      }
    })
  }

  Promise.race = function(promises) {
    return new Promise(function(resolve, reject) {
      for (var i = 0; i < promises.length; i++) {
        Promise.resolve(promises[i]).then(function(value) {
          return resolve(value)
        }, function(reason) {
          return reject(reason)
        })
      }
    })
  }

  Promise.resolve = function(value) {
    return new Promise(function(resolve) {
      resolve(value)
    })
  }

  Promise.reject = function(reason) {
    return new Promise(function(resolve, reject) {
      reject(reason)
    })
  }

  Promise.fcall = function(fn){
    // 雖然fn可以接收到上一層then里傳來的參數(shù),但是其實是undefined讹挎,所以跟沒有是一樣的校赤,因為resolve沒參數(shù)啊
    return Promise.resolve().then(fn)
  }

  Promise.done = Promise.stop = function(){
    return new Promise(function(){})
  }

  Promise.deferred = Promise.defer = function() {
    var dfd = {}
    dfd.promise = new Promise(function(resolve, reject) {
      dfd.resolve = resolve
      dfd.reject = reject
    })
    return dfd
  }

  try { // CommonJS compliance
    module.exports = Promise
  } catch(e) {}

  return Promise
})()

最后提一下值穿透:

var promise = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('haha')
  }, 1000)
})
promise
  .then('hehe')
  .then(console.log)

最終打印 haha 而不是 hehe

Generator:

說Generator之前,還是談談Iterator 遍歷器比較好

Symbol是一個特殊的數(shù)據(jù)類型筒溃,和number string等并列

console.log(Array.prototype.slice)  // ? slice() { [native code] }
console.log(Array.prototype[Symbol.iterator])  
// ? values() { [native code] }

我們獲取Array.prototype[Symbol.iterator]可以得到一個函數(shù)马篮,只不過這里的[Symbol.iterator]是Symbol數(shù)據(jù)類型,不是字符串怜奖。但是沒關系浑测,Symbol數(shù)據(jù)類型也可以作為對象屬性的key

var obj = {}
obj.a = 100
obj[Symbol.iterator] = 200
console.log(obj)  // {a: 100, Symbol(Symbol.iterator): 200}

只需要知道[Symbol.iterator]是一個特殊的數(shù)據(jù)類型Symbol類型,但是也可以像number string類型一樣歪玲,作為對象的屬性key來使用

原生具有[Symbol.iterator]屬性數(shù)據(jù)類型有:數(shù)組迁央、某些類似數(shù)組的對象(如arguments、NodeList)滥崩、Set和Map

原生具有[Symbol.iterator]屬性數(shù)據(jù)類型有一個特點岖圈,就是可以使用for...of來取值

var item
for (item of [100, 200, 300]) {
    console.log(item)
}
// 打印出:100 200 300 
// 注意,這里每次獲取的 item 是數(shù)組的 value夭委,而不是 index 幅狮,這一點和 傳統(tǒng) for 循環(huán)以及 for...in 完全不一樣

而具有[Symbol.iterator]屬性的對象,都可以一鍵生成一個Iterator對象

const arr = [100, 200, 300]
const iterator = arr[Symbol.iterator]()  // 通過執(zhí)行 [Symbol.iterator] 的屬性值(函數(shù))來返回一個 iterator 對象

現(xiàn)在生成了iterator株灸,那么該如何使用它呢 ———— 有兩種方式:next和for...of

console.log(iterator.next())  // { value: 100, done: false }
console.log(iterator.next())  // { value: 200, done: false }
console.log(iterator.next())  // { value: 300, done: false }
console.log(iterator.next())  // { value: undefined, done: true }

iterator對象可以通過next()方法逐步獲取每個元素的值崇摄,以{ value: ..., done: ... }形式返回,value就是值慌烧,done表示是否到已經獲取完成

再說第二種逐抑,for...of

let i
for (i of iterator) {
    console.log(i)
}
// 打印:100 200 300

上面使用for...of遍歷iterator對象屹蚊,可以直接將其值獲取出來厕氨。這里的“值”就對應著上面next()返回的結果的value屬性

Generator返回的也是Iterator對象,因此才會有next(),也可以通過for...of來遍歷

正是因為Generator返回的是Iterator對象汹粤,所以我先講生成器對象

先來一段最基礎的Generator代碼

function* Hello() {
    yield 100
    yield (function () {return 200})()
    return 300
}

var h = Hello()
console.log(typeof h)  // object

console.log(h.next())  // { value: 100, done: false }
console.log(h.next())  // { value: 200, done: false }
console.log(h.next())  // { value: 300, done: true }
console.log(h.next())  // { value: undefined, done: true }
定義Generator時命斧,需要使用function*,其他的和定義函數(shù)一樣嘱兼。內部使用yield国葬,至于yield的用處以后再說

執(zhí)行var h = Hello()生成一個Generator對象,經驗驗證typeof h發(fā)現(xiàn)不是普通的函數(shù)

執(zhí)行Hello()之后芹壕,Hello內部的代碼不會立即執(zhí)行汇四,而是出于一個暫停狀態(tài)

執(zhí)行第一個h.next()時,會激活剛才的暫停狀態(tài)踢涌,開始執(zhí)行Hello內部的語句通孽,但是,直到遇到y(tǒng)ield語句睁壁。一旦遇到y(tǒng)ield語句時背苦,它就會將yield后面的表達式執(zhí)行,并返回執(zhí)行的結果堡僻,然后又立即進入暫停狀態(tài)糠惫。

因此第一個console.log(h.next())打印出來的是{ value: 100, done: false },value是第一個yield返回的值钉疫,done: false表示目前處于暫停狀態(tài)硼讽,尚未執(zhí)行結束,還可以再繼續(xù)往下執(zhí)行牲阁。

執(zhí)行第二個h.next()和第一個一樣固阁,不在贅述。此時會執(zhí)行完第二個yield后面的表達式并返回結果城菊,然后再次進入暫停狀態(tài)

執(zhí)行第三個h.next()時备燃,程序會打破暫停狀態(tài),繼續(xù)往下執(zhí)行凌唬,但是遇到的不是yield而是return并齐。這就預示著,即將執(zhí)行結束了。因此最后返回的是{ value: 300, done: true }况褪,done: true表示執(zhí)行結束撕贞,無法再繼續(xù)往下執(zhí)行了。

再去執(zhí)行第四次h.next()時测垛,就只能得到{ value: undefined, done: true }捏膨,因為已經結束,沒有返回值了

需要明白以下幾點:

Generator不是函數(shù)食侮,不是函數(shù)号涯,不是函數(shù)
Hello()不會立即出發(fā)執(zhí)行,而是一上來就暫停

每次h.next()都會打破暫停狀態(tài)去執(zhí)行锯七,直到遇到下一個yield或者return

遇到y(tǒng)ield時链快,會執(zhí)行yeild后面的表達式,并返回執(zhí)行之后的值眉尸,然后再次進入暫停狀態(tài)久又,此時done: false。

遇到return時效五,會返回值地消,執(zhí)行結束,即done: true

每次h.next()的返回值永遠都是{value: ... , done: ...}的形式

第一個next()無需傳入?yún)?shù)畏妖,它總是啟動一個生成器脉执,并運行到第一個yield處,不過戒劫,第二個next(..)調用第一個暫停的yield表達式半夷,第三個next(..)調用第二個暫停的yield表達式,最后多出了一個next(),有return來回答它

生成器消息是雙向傳遞的迅细,yield(...)作為一個表達式可以發(fā)出消息響應next.value()巫橄,next(...)也可以向暫停的yield表達式發(fā)送值,可以看下面這個demo

function *(){
    var y = x * (yield "hello")
    return y
}
var it = foo(6) //將6傳給x
var res = it.next() //啟動生成器
res.value() //hello
res = it.next(7) 向等待的yield傳入7
res.value()//42
function* G() {
    const a = yield 100
    console.log('a', a)  // a aaa
    const b = yield 200
    console.log('b', b)  // b bbb
    const c = yield 300
    console.log('c', c)  // c ccc
}
const g = G()
g.next()    // value: 100, done: false
g.next('aaa') // value: 200, done: false
g.next('bbb') // value: 300, done: false
g.next('ccc') // value: undefined, done: true
  • 執(zhí)行第一個g.next()時茵典,為傳遞任何參數(shù)湘换,返回的{value: 100, done: false},這個應該沒有疑問
  • 執(zhí)行第二個g.next('aaa')時统阿,傳遞的參數(shù)是'aaa'彩倚,這個'aaa'就會被賦值到G內部的a標量中,然后執(zhí)行console.log('a', a)打印出來扶平,最后返回{value: 200, done: false}
  • 執(zhí)行第三個帆离、第四個時,道理都是完全一樣的结澄,大家自己捋一捋

有一個要點需要注意哥谷,就g.next('aaa')是將'aaa'傳遞給上一個已經執(zhí)行完了的yield語句前面的變量岸夯,而不是即將執(zhí)行的yield前面的變量。這句話要能看明白

function* fibonacci() {
    let [prev, curr] = [0, 1]
    for (;;) {
        [prev, curr] = [curr, prev + curr]
        // 將中間值通過 yield 返回们妥,并且保留函數(shù)執(zhí)行的狀態(tài)囱修,因此可以非常簡單的實現(xiàn) fibonacci
        yield curr
    }
}
for (let n of fibonacci()) {
    if (n > 1000) {
        break
    }
    console.log(n)
}

如果有兩個Generator,想要在第一個中包含第二個

function* G1() {
    yield 'a'
    yield* G2()  // 使用 yield* 執(zhí)行 G2()
    yield 'b'
}
function* G2() {
    yield 'x'
    yield 'y'
}
for (let item of G1()) {
    console.log(item)

yield后面會接一個普通的 JS 對象王悍,而yield后面會接一個Generator,而且會把它其中的yield按照規(guī)則來一步一步執(zhí)行餐曼。如果有多個Generator串聯(lián)使用的話(例如Koa源碼中)压储,用yield來操作非常方便

Thunk 函數(shù):

往往是將參數(shù)放到一個臨時函數(shù)之中,再將這個臨時函數(shù)傳入函數(shù)體源譬。這個臨時函數(shù)就叫做 Thunk 函數(shù)

function f(m){
  return m * 2;     
}

f(x + 5);

// 等同于

var thunk = function () {
  return x + 5;
};

function f(thunk){
  return thunk() * 2;
}
const thunk = function (fileName, codeType) {
    // 返回一個只接受 callback 參數(shù)的函數(shù)
    return function (callback) {
        fs.readFile(fileName, codeType, callback)
    }
}
const readFileThunk = thunk('data1.json', 'utf-8')
readFileThunk((err, data) => {
    // 獲取文件內容
})

執(zhí)行const readFileThunk = thunk('data1.json', 'utf-8')返回的其實是一個函數(shù)
readFileThunk這個函數(shù)集惋,只接受一個參數(shù),而且這個參數(shù)是一個callback函數(shù)

就上上面的代碼踩娘,我們經過對傳統(tǒng)的異步操作函數(shù)進行封裝刮刑,得到一個只有一個參數(shù)的函數(shù),而且這個參數(shù)是一個callback函數(shù)养渴,那這就是一個thunk函數(shù)雷绢。就像上面代碼中readFileThunk一樣

在Genertor中使用thunk函數(shù)

const readFileThunk = thunkify(fs.readFile)
const gen = function* () {
    const r1 = yield readFileThunk('data1.json')
    console.log(r1)
    const r2 = yield readFileThunk('data2.json')
    console.log(r2)
}

挨個讀取兩個文件的內容

復制代碼
const g = gen()

// 試著打印 g.next() 這里一定要明白 value 是一個 thunk函數(shù) ,否則下面的代碼你都看不懂
// console.log( g.next() )  // g.next() 返回 {{ value: thunk函數(shù), done: false }} 

// 下一行中理卑,g.next().value 是一個 thunk 函數(shù)翘紊,它需要一個 callback 函數(shù)作為參數(shù)傳遞進去
g.next().value((err, data1) => {
    // 這里的 data1 獲取的就是第一個文件的內容。下一行中藐唠,g.next(data1) 可以將數(shù)據(jù)傳遞給上面的 r1 變量帆疟,此前已經講過這種參數(shù)傳遞的形式
    // 下一行中,g.next(data1).value 又是一個 thunk 函數(shù)宇立,它又需要一個 callback 函數(shù)作為參數(shù)傳遞進去
    g.next(data1).value((err, data2) => {
        // 這里的 data2 獲取的是第二個文件的內容踪宠,通過 g.next(data2) 將數(shù)據(jù)傳遞個上面的 r2 變量
        g.next(data2)
    })
})

自驅動流程:

// 自動流程管理的函數(shù)
function run(generator) {
    const g = generator()
    function next(err, data) {
        const result = g.next(data)  // 返回 { value: thunk函數(shù), done: ... }
        if (result.done) {
            // result.done 表示是否結束,如果結束了那就 return 作罷
            return
        }
        result.value(next)  // result.value 是一個 thunk 函數(shù)妈嘹,需要一個 callback 函數(shù)作為參數(shù)柳琢,而 next 就是一個 callback 形式的函數(shù)
    }
    next() // 手動執(zhí)行以啟動第一次 next
}

// 定義 Generator
const readFileThunk = thunkify(fs.readFile)
const gen = function* () {
    const r1 = yield readFileThunk('data1.json')
    console.log(r1.toString())
    const r2 = yield readFileThunk('data2.json')
    console.log(r2.toString())
}

// 啟動執(zhí)行
run(gen)

其實這段代碼和上面的手動編寫讀取兩個文件內容的代碼,原理上是一模一樣的润脸,只不過這里把流程驅動給封裝起來了染厅。我們簡單分析一下這段代碼

  • 最后一行run(gen)之后,進入run函數(shù)內部執(zhí)行

  • 先const g = generator()創(chuàng)建Generator實例津函,然后定義一個next方法肖粮,并且立即執(zhí)行next()

  • 注意這個next函數(shù)的參數(shù)是err, data兩個,和我們fs.readFile用到的callback函數(shù)形式完全一樣

  • 第一次執(zhí)行next時尔苦,會執(zhí)行const result = g.next(data)涩馆,而g.next(data)返回的是{ value: thunk函數(shù), done: ... }行施,value是一個thunk函數(shù),done表示是否結束

  • 如果done: true魂那,那就直接return了蛾号,否則繼續(xù)進行

  • result.value是一個thunk函數(shù),需要接受一個callback函數(shù)作為參數(shù)傳遞進去涯雅,因此正好把next給傳遞進去鲜结,讓next一直被執(zhí)行下去

koa 中如何應用Generator:

oa 是一個 web 框架,處理 http 請求活逆,但是這里我們不去管它如何處理 http 請求精刷,而是直接關注它使用Genertor的部分————中間件

let info = ''
function* g1() {
    info += '1'  // 拼接 1
    yield* g2()  // 拼接 234
    info += '5'  // 拼接 5
}
function* g2() {
    info += '2'  // 拼接 2
    yield* g3()  // 拼接 3
    info += '4'  // 拼接 4
}
function* g3() {
    info += '3'  // 拼接 3
}

var g = g1()
g.next()
console.log(info)  // 12345

但是如果用 koa 的 中間件 的思路來做,就需要如下這么寫

app.use(function *(next){
    this.body = '1';
    yield next;
    this.body += '5';
    console.log(this.body);
});
app.use(function *(next){
    this.body += '2';
    yield next;
    this.body += '4';
});
app.use(function *(next){
    this.body += '3';
});

app.use()中傳入的每一個Generator就是一個 中間件蔗候,中間件按照傳入的順序排列怒允,順序不能亂

每個中間件內部,next表示下一個中間件锈遥。yield next就是先將程序暫停纫事,先去執(zhí)行下一個中間件,等next被執(zhí)行完之后所灸,再回過頭來執(zhí)行當前代碼的下一行丽惶。因此,koa 的中間件執(zhí)行順序是一種洋蔥圈模型爬立,不過這里看不懂也沒問題蚊夫。

每個中間件內部,this可以共享變量懦尝。即第一個中間件改變了this的屬性知纷,在第二個中間件中可以看到效果

koa 的這種應用機制是如何實現(xiàn)的

class MyKoa extends Object {
    constructor(props) {
        super(props);

        // 存儲所有的中間件
        this.middlewares = []
    }

    // 注入中間件
    use (generator) {
        this.middlewares.push(generator)
    }

    // 執(zhí)行中間件
    listen () {
        this._run()
    }

    _run () {
        const ctx = this
        const middlewares = ctx.middlewares
        co(function* () {
            let prev = null
            let i = middlewares.length
            //從最后一個中間件到第一個中間件的順序開始遍歷
            while (i--) {
                // ctx 作為函數(shù)執(zhí)行時的 this 才能保證多個中間件中數(shù)據(jù)的共享
                //prev 將前面一個中間件傳遞給當前中間件,才使得中間件里面的 next 指向下一個中間件
                prev = middlewares[i].call(ctx, prev);
            }
            //執(zhí)行第一個中間件
            yield prev;
        })
    }
}
var app = new MyKoa();
app.use(function *(next){
    this.body = '1';
    yield next;
    this.body += '5';
    console.log(this.body);  // 12345
});
app.use(function *(next){
    this.body += '2';
    yield next;
    this.body += '4';
});
app.use(function *(next){
    this.body += '3';
});
app.listen();

Promise其實是利用了callback才能實現(xiàn)的陵霉。而這里琅轧,Generator也必須利用callback才能實現(xiàn),如果yield后面用的是thunk函數(shù),那么thunk函數(shù)需要的就是一個callback參數(shù)踊挠。如果yield后面用的是Promise對象

因此乍桂,Generator離不開callback,Promise離不開callback效床,異步也離不開callback

co(function* () {
    const r1 = yield readFilePromise('some1.json')
    console.log(r1)  // 打印第 1 個文件內容
    const r2 = yield readFilePromise('some2.json')
    console.log(r2)  // 打印第 2 個文件內容
})

再來一段async-await的執(zhí)行代碼如下睹酌,兩者做一個比較。

const readFilePromise = Q.denodeify(fs.readFile)

// 定義 async 函數(shù)
const readFileAsync = async function () {
    const f1 = await readFilePromise('data1.json')
    const f2 = await readFilePromise('data2.json')
    console.log('data1.json', f1.toString())
    console.log('data2.json', f2.toString())

    return 'done' // 先忽略剩檀,后面會講到
}
// 執(zhí)行
const result = readFileAsync()

從上面兩端代碼比較看來憋沿,async function代替了function*,await代替了yield沪猴,其他的再沒有什么區(qū)別了辐啄。哦采章,還有,使用async-await時候不用再引用co這種第三方庫了壶辜,直接執(zhí)行即可

使用async-await的不同和好處:

  1. await后面不能再跟thunk函數(shù)悯舟,而必須跟一個Promise對象(因此,Promise才是異步的終極解決方案和未來)砸民。跟其他類型的數(shù)據(jù)也OK抵怎,但是會直接同步執(zhí)行,而不是異步

  2. 執(zhí)行const result = readFileAsync()返回的是個Promise對象岭参,而且上面代碼中的return 'done'會直接被下面的then函數(shù)接收到執(zhí)行const result = readFileAsync()返回的是個Promise對象反惕,而且上面代碼中的return 'done'會直接被下面的then函數(shù)接收到

result.then(data => {
    console.log(data)  // done
})
  1. 從代碼的易讀性來將,async-await更加易讀簡介冗荸,也更加符合代碼的語意。而且還不用引用第三方庫利耍,也無需學習Generator那一堆東西蚌本,使用成本非常低

異步操作代碼的變化:

callback方式:

fs.readFile('some1.json', (err, data) => {
    fs.readFile('some2.json', (err, data) => {
        fs.readFile('some3.json', (err, data) => {
            fs.readFile('some4.json', (err, data) => {

            })
        })
    })
})

Promise方式:

readFilePromise('some1.json').then(data => {
    return readFilePromise('some2.json')
}).then(data => {
    return readFilePromise('some3.json')
}).then(data => {
    return readFilePromise('some4.json')
})

Generator方式:

co(function* () {
    const r1 = yield readFilePromise('some1.json')
    const r2 = yield readFilePromise('some2.json')
    const r3 = yield readFilePromise('some3.json')
    const r4 = yield readFilePromise('some4.json')
})

async-await方式:

const readFileAsync = async function () {
    const f1 = await readFilePromise('data1.json')
    const f2 = await readFilePromise('data2.json')
    const f3 = await readFilePromise('data3.json')
    const f4 = await readFilePromise('data4.json')
}

以下是參考鏈接:

深入理解js異步系列

event loop

阮一峰

史上最易讀懂的 Promise/A+ 完全實現(xiàn)

Promise 實現(xiàn)詳解

大白話講解Promise

八段代碼徹底掌握 Promise

深入核心,詳解事件循環(huán)機制

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末隘梨,一起剝皮案震驚了整個濱河市程癌,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌轴猎,老刑警劉巖嵌莉,帶你破解...
    沈念sama閱讀 206,126評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異捻脖,居然都是意外死亡锐峭,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評論 2 382
  • 文/潘曉璐 我一進店門可婶,熙熙樓的掌柜王于貴愁眉苦臉地迎上來沿癞,“玉大人,你說我怎么就攤上這事矛渴∽笛铮” “怎么了?”我有些...
    開封第一講書人閱讀 152,445評論 0 341
  • 文/不壞的土叔 我叫張陵具温,是天一觀的道長蚕涤。 經常有香客問我,道長铣猩,這世上最難降的妖魔是什么揖铜? 我笑而不...
    開封第一講書人閱讀 55,185評論 1 278
  • 正文 為了忘掉前任,我火速辦了婚禮达皿,結果婚禮上蛮位,老公的妹妹穿的比我還像新娘较沪。我一直安慰自己,他們只是感情好失仁,可當我...
    茶點故事閱讀 64,178評論 5 371
  • 文/花漫 我一把揭開白布尸曼。 她就那樣靜靜地躺著,像睡著了一般萄焦。 火紅的嫁衣襯著肌膚如雪控轿。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 48,970評論 1 284
  • 那天拂封,我揣著相機與錄音茬射,去河邊找鬼。 笑死冒签,一個胖子當著我的面吹牛在抛,可吹牛的內容都是我干的。 我是一名探鬼主播萧恕,決...
    沈念sama閱讀 38,276評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼刚梭,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了票唆?” 一聲冷哼從身側響起朴读,我...
    開封第一講書人閱讀 36,927評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎走趋,沒想到半個月后衅金,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經...
    沈念sama閱讀 43,400評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡簿煌,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 35,883評論 2 323
  • 正文 我和宋清朗相戀三年氮唯,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片姨伟。...
    茶點故事閱讀 37,997評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡您觉,死狀恐怖,靈堂內的尸體忽然破棺而出授滓,到底是詐尸還是另有隱情琳水,我是刑警寧澤,帶...
    沈念sama閱讀 33,646評論 4 322
  • 正文 年R本政府宣布般堆,位于F島的核電站在孝,受9級特大地震影響,放射性物質發(fā)生泄漏淮摔。R本人自食惡果不足惜私沮,卻給世界環(huán)境...
    茶點故事閱讀 39,213評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望和橙。 院中可真熱鬧仔燕,春花似錦造垛、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,204評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至外恕,卻和暖如春杆逗,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背鳞疲。 一陣腳步聲響...
    開封第一講書人閱讀 31,423評論 1 260
  • 我被黑心中介騙來泰國打工罪郊, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人尚洽。 一個月前我還...
    沈念sama閱讀 45,423評論 2 352
  • 正文 我出身青樓悔橄,卻偏偏與公主長得像,于是被迫代替她去往敵國和親腺毫。 傳聞我的和親對象是個殘疾皇子癣疟,可洞房花燭夜當晚...
    茶點故事閱讀 42,722評論 2 345

推薦閱讀更多精彩內容