我對Promises的理解

本文作者就是我,簡書的microkof。如果您覺得本文對您的工作有意義,產生了不可估量的價值,那么請您不吝打賞我,謝謝!

名詞約定

Promises的概念是由CommonJS小組的成員在Promises/A規范中提出來的。一般來講,有以下的名詞約定:

promise(首字母小寫)對象指的是Promise實例對象

Promise首字母大寫且單數形式,表示Promise構造函數

Promises首字母大寫且復數形式,用于指代Promises規范

Promises/A規范和ES6 Promises規范

Promise規范有幾次升級,目前來講,Promise/A是最新的民間規范,ES6 Promises是最新的官方規范,只需知道ES6 Promises規范是你應該遵守的標準就行了。

為什么有Promises這個東西

  1. 解決回調金字塔的問題,回調金字塔也叫回調圣誕樹,好聽吧?還有個別名叫回調地獄,不好聽了吧?到底可怕不可怕?非常的可怕。
  2. 可以同時管理成功回調和失敗回調。

名詞解釋:同步任務和異步任務

  • 同步任務:只需JS引擎自身就可以完成的任務,叫同步任務。
  • 異步任務:JS引擎自身無法完成,需要外力協助的任務,叫異步任務;還有一種是由JS引擎能夠獨立完成,但是JS引擎把任務分成了兩段,第二段當做回調,也是異步任務。

簡單理解JS引擎的執行機制的話,異步任務分為三個執行段,對JS引擎來講是執行第一和第三個階段:

  1. 異步任務本體執行:由JS引擎同步執行
  2. 異步任務外力執行:由外力執行
  3. 異步任務回調執行:由JS引擎異步執行

所謂“承諾”

Promise這個單詞的意思是“承諾”,就是我們日常說的“我肯定幫你買早飯”、“你如果交了錢我肯定給你一杯咖啡”。

在程序世界,舉例可以說:“我承諾給你完成這些代碼執行”。new一個Promise實例,就是JS引擎對你做了一個承諾。

既然是承諾,就肯定有成功的時候有失敗的時候,比如我幫你買早餐,結果今天煎餅果子沒出攤,或者是我走到半路上,煎餅果子的塑料袋破裂,煎餅果子滑落到了地上,這就是失敗。就連“我們承諾絕不首先動用核武器”都有堅持不下去的時候,所以只要是承諾就有成功和失敗,只不過是概率問題。

到程序世界,一個承諾也會有三種狀態,就是“未決的”、“成功的”、“失敗的”三種狀態。也就是pending/resolved/rejected三種狀態。

Promise構造函數的超能力

Promises寫法的本質就是把異步寫法擼成同步寫法。要做這么酷炫這么變態的事情,當然需要Promise構造函數有超能力,它的超能力就是傳入Promise構造函數的函數參數會第一優先執行,無論這個函數多么的繁復,有多少層回調,有多少秒的計數器,統統都會最優先執行,也就是說,我們只要new了一個Promise(),那么Promise構造函數的函數參數其實是同步代碼,但是.then比較特殊,.then會等到promise對象實例有了結果(resolved或者rejected),.then()里面代碼才會執行。鏈條上的每一個.then都會等前面的promise有了結果才會執行,Promise構造函數的這個超能力是Promises系統的威力之源。(當然,這里說的執行優先級,是在理想環境下,所謂理想環境也就是全部執行代碼只由new Promise()和它的一系列.then()方法組成。如果方法鏈之外還有其他代碼,那么整體代碼執行的先后順序就復雜化了,涉及到ES最底層的Event Loop,下文有介紹。然而,Promise加它的then方法鏈已經提供了梳理代碼執行順序的整套方案,如果在方法鏈之外還寫異步代碼的話屬于不鼓勵的寫法,應該盡量避免這么做。)

實現了Promise/A規范的瀏覽器

簡單說,IE全不支持,Edge支持,Chrome和Firefox在十幾個版本之前就已經接近支持,目前的最新版已經全面支持。

所以,IE8-10可以考慮Promise/A的Polyfill庫:

jakearchibald/es6-promise
一個兼容 ES6 Promises 的Polyfill類庫。 它基于 RSVP.js 這個兼容 Promises/A+ 的類庫, 它只是 RSVP.js 的一個子集,只實現了Promises 規定的 API。

yahoo/ypromise
這是一個獨立版本的 YUI 的 Promise Polyfill,具有和 ES6 Promises 的兼容性。 本書的示例代碼也都是基于這個 ypromise 的 Polyfill 來在線運行的。

getify/native-promise-only
以作為ES6 Promises的polyfill為目的的類庫 它嚴格按照ES6 Promises的規范設計,沒有添加在規范中沒有定義的功能。 如果運行環境有原生的Promise支持的話,則優先使用原生的Promise支持。

其他還有很多Polyfill類庫,不多說,可以github一下。

基本用法

案例1:現在開始,延遲3秒,執行console.log('第一個回調'),然后定義一個變量a,值是3,然后再延遲2秒,執行console.log('第二個回調'),然后執行console.log(a * 2)。回調圣誕樹型寫法是:

setTimeout(function() {
    console.log('第一個回調');
    var a = 3;
    setTimeout(function() {
        console.log('第二個回調');
        console.log(a * 2);
    }, 2000);
}, 3000);

執行上面代碼,結果是:先延遲3秒,然后瀏覽器打印了一個第一個回調字符串,然后又延遲2秒,然后瀏覽器打印了一個第二個回調字符串,以及打印了一個6數字。

然后遵循Promises的寫法是:

var promise = new Promise(function(resolve, reject) {
    setTimeout(function() {
        console.log('第一個回調');
        var a = 3;
        if ( true ){
            resolve(a);
        } else {
            reject('bu ok');
        }
    }, 3000);
});

promise.then(function(value) {
    setTimeout(function() {
        console.log('第二個回調');
        console.log(value * 2);
    }, 2000);
}, function(error) {
      console.log(error);
});

執行結果跟圣誕樹寫法的結果完全一致。

Promise是一個構造函數,用來生成promise實例。Promise構造函數接受一個函數作為參數,該函數的兩個參數分別是resolve和reject。它們是兩個函數,由JavaScript引擎提供,不用自己部署。

promise對象代表一個異步操作,有三種狀態:Pending(進行中)、Resolved
(已完成)和Rejected(已失敗)。

  • resolve函數的作用是,將promise對象的狀態從“未完成”變為“成功”(即從Pending變為Resolved),在異步操作成功時調用,并將異步操作的結果,作為參數傳遞出去。
  • reject函數的作用是,將promise對象的狀態從“未完成”變為“失敗”(即從Pending變為Rejected),在異步操作失敗時調用,并將異步操作報出的錯誤,作為參數傳遞出去。

promise對象生成以后,可以用then方法分別指定Resolved狀態和Reject狀態的回調函數。

then方法可以接受兩個回調函數作為參數。第一個回調函數是promise對象的狀態變為Resolved時調用,第二個回調函數是promise對象的狀態變為Reject時調用。其中,第二個函數是可選的,不一定要提供。這兩個函數都接受promise對象傳出的值作為參數。

然后這里你可能會問,if ( true ){}是什么鬼?因為console.log('第一個對象');var a = 3;是不可能失敗的,這都能失敗的話,等于js引擎掛了,也等于頁面掛了。所以我只能模擬成功和失敗狀態,現在你把true改成false試試,那么,延遲2秒之后是不是打印了bu ok

比較兩種寫法的區別,首先就可以看出Promises寫法的代碼多了很多。如果你確定promise對象根本不可能有失敗的狀態,可以省掉reject函數以及錯誤回調。那么可以簡寫成這樣:

var promise = new Promise(function(resolve) {
    setTimeout(function() {
        console.log('第一個回調');
        resolve(3);
    }, 3000);
});

promise.then(function(value) {
    setTimeout(function() {
        console.log('第二個回調');
        console.log(value * 2);
    }, 2000);
});

上面代碼就是只考慮promise對象“成功”的可能性,不考慮失敗的可能性。

去掉promise對象失敗的可能性之后,你可能繼續會說,“Promises寫法的代碼還是多!”沒錯,確實多,但不要只看劣勢不看優勢,沒有優勢的東西,是沒人會用的。假設回調多起來了,比如至少5個,而且每一步回調都有成功和失敗狀態,那么Promises的優勢才能顯現出來。

案例2:在案例1的基礎上,再延遲1秒,執行console.log('第三個回調'); console.log(value * 2);,也就是說,我想在第二步的輸出值的基礎上再乘以2,也就是想得到12。代碼如下:

var promise = new Promise(function(resolve, reject) {
    setTimeout(function() {
        console.log('第一個回調');
        resolve(3);
    }, 3000);
});

promise.then(function(value) {
    return new Promise(function(resolve, reject) {
        setTimeout(function() {
            console.log('第二個回調');
            console.log(value * 2);
            resolve(value * 2);
        }, 2000);
    });
}).then(function(value) {
    setTimeout(function() {
        console.log('第三個回調');
        console.log(value * 2);
    }, 1000);
});

結果沒問題:

Paste_Image.png

這里用到了then的鏈式調用。你會發現第一個then返回了一個promise對象。這就跟案例1不一樣了,案例一的then里沒有再返回promise對象。必須返回么?看案例3。

案例3:跟案例2相似,只是執行console.log('第二個回調')這步不用延遲。代碼如下:

var promise = new Promise(function(resolve, reject) {
    setTimeout(function() {
        console.log('第一個回調');
        resolve(3);
    }, 3000);
});

promise.then(function(value) {
    console.log('第二個回調');
    console.log(value * 2);
    return value * 2;
}).then(function(value) {
    setTimeout(function() {
        console.log('第三個回調');
        console.log(value * 2);
    }, 1000);
});

跟案例2的代碼的區別是什么?第一個then方法中,沒了return promise對象,取而代之的是return value * 2,為什么?

因為案例2中,三個then的回調函數是異步-異步-異步,案例3中,是異步-同步-異步,這區別很大。簡單情況下,then方法中的回調執行代碼是同步代碼,這樣只需要簡單return一下參數,就可以把參數傳遞下去。復雜情況下,是異步-異步-異步這種情況,如果依然簡單的在setTimeout的回調里return一下參數,你會發現,參數根本沒有及時傳遞。代碼如下:

var promise = new Promise(function(resolve, reject) {
    setTimeout(function() {
        console.log('第一個回調');
        resolve(3);
    }, 3000);
});

promise.then(function(value) {
    setTimeout(function() {
        console.log('第二個回調');
        console.log(value * 2);
        return value * 2;
    }, 2000);
}).then(function(value) {
    setTimeout(function() {
        console.log('第三個回調');
        console.log(value * 2);
    }, 1000);
});

結果就神奇了:

Paste_Image.png

什么原因?怎么理解?

簡單說原因是:

then的鏈式執行,理想情況下是基于每個then的前一個then能夠返回promise對象。不理想情況下是沒有返回promise對象,這種情況下,雖然then的鏈式執行依然可以執行,但是,每個then只可能等前一個then同步代碼完成,不會等前一個then異步代碼完成。第一個then的同步代碼是一個計時器,開始計時就算完成了,然后第二個then什么也沒得到,其實是得到了一個undefinedundefined * 2得到NaN,所以打印NaN

為啥第一個then的回調用return promise對象,第二個then就可以等那2秒的延遲呢?用上段文字就很好理解了,而且你還可以回憶一下本文最上方說的promise對象的超能力,當你給then返回一個非promise對象,then只接收同步的返回值,反之,當你給then返回一個promise對象,那么then就等待promise對象生成,然后等resolvereject傳遞參數,等多久都能等。

這里要注意一下,我上段文字所說的“等待”,其實并不是等待,而是new一個promise對象的過程被js引擎視為同步任務執行,因此new出了promise對象,并return的過程,其實是同步代碼,then其實并不是在等待,而是非常自然的鏈式執行順序。

為什么第三個回調比第二個回調先執行了?因為第二個then得到了undefined之后,第三個then就開始了,第三個回調延遲的時間短嘛,就1秒,所以比第二個回調先執行了。

為啥“第二個回調”下面輸出是6?因為3 * 2得6。

總之,如果想異步-異步-異步-異步......這樣一直搞下去,就只能是每一步都給下一步返回一個promise對象。

解答幾個問題:

如果resolvereject語句后面還寫了語句,會執行嗎?

會。resolvereject負責傳參,但不是說傳了參就中止執行了。

如果第一個then的回調用了promise對象,但是promise對象沒寫resolvereject方法,第二個then的回調還會執行么?

答案是不執行,等于第二個then白寫了,因為promise對象永遠處于Pending狀態。如果后面有第三個then,依然不會執行。等于鏈條從第一個promise對象就斷了。

如果有resolvereject方法,但是不設參數,也就是resolve()reject(),那么then會執行嗎?

會。resolvereject傳的參數是undefined

如果第一個then的第一個回調函數沒執行,第二個then的第一個回調函數會執行么?

會。第二個then的第一個回調,并不是前一個then的第一個回調的繼承。

每個then的2個回調,只可能有其中一個回調執行。下一個then的第一個回調,只會看前一個then的任意回調是否返回成功的promise;下一個then的第二個回調,只會看前一個then的任意回調是否返回失敗的promise。

如果promise只執行了reject方法,但第一個then沒有寫對應的error處理回調,第二個then寫了,還能處理么?

能。Promises規定,參數可以無限制的順著鏈條傳遞下去直到被處理掉。

如果流程是異步-同步-同步-同步.....這么走下去,那么搞一串then還有意義嗎?所有同步合在一起豈不是更容易編寫?也容易理解?

答案:如果真的是一串的同步,當然可以合并了。Promises的用武之地在于全異步或者異步-同步互相夾雜的情況。

then的回調的優先級有多高?

測試一下:

console.log('sync1');

setTimeout(function() {console.log('setTimeout1')}, 0);

var promise = new Promise(function(resolve, reject) {
    setTimeout(function() {console.log('setTimeoutPromise')}, 0);
    console.log('promise');
    resolve();
});

promise.then(function() {
    setTimeout(function() {console.log('setTimeoutThen-1')}, 0);
    console.log('then-1');
}).then(function() {
    setTimeout(function() {console.log('setTimeoutThen-2')}, 0);
    console.log('then-2');
});

setTimeout(function() {console.log('setTimeout2')}, 0);

console.log('sync2');

會得到

sync1
promise
sync2
then-1
then-2
setTimeout1
setTimeoutPromise
setTimeout2
setTimeoutThen-1
setTimeoutThen-2

這里就要科普一下ES的Event Loop,Event Loop簡單說就是ES為了高效解決異步任務而制定的一套規則,它的基本含義這里不講,可以自行網上搜索,也可以參考https://segmentfault.com/a/1190000016278115這篇文章,這里只摘抄結論:

ES的引擎里有2個隊列:

一個叫宏隊列,macrotask,也叫tasks。 一些異步任務的回調會依次進入macro task queue,等待后續被調用,這些異步任務包括:

  • setTimeout
  • setInterval
  • setImmediate (Node獨有)
  • requestAnimationFrame (瀏覽器獨有)
  • I/O
  • UI rendering (瀏覽器獨有)

另一個叫微隊列,microtask,也叫jobs。 另一些異步任務的回調會依次進入micro task queue,等待后續被調用,這些異步任務包括:

  • process.nextTick (Node獨有)
  • Promise
  • Object.observe
  • MutationObserver

那么,ES整個的任務隊列的執行機制就是:

  1. 執行全局Script同步代碼,這些同步代碼有一些是同步語句,有一些是異步語句(比如setTimeout等);
  2. 全局Script代碼執行完畢后,調用棧Stack會清空;
  3. 從微隊列microtask queue中取出位于隊首的回調任務,放入調用棧Stack中執行,執行完后microtask queue長度減1;
  4. 繼續取出位于隊首的任務,放入調用棧Stack中執行,以此類推,直到直到把microtask queue中的所有任務都執行完畢。注意,如果在執行microtask的過程中,又產生了microtask,那么會加入到隊列的末尾,也會在這個周期被調用執行;
  5. microtask queue中的所有任務都執行完畢,此時microtask queue為空隊列,調用棧Stack也為空;
  6. 取出宏隊列macrotask queue中位于隊首的任務,放入Stack中執行;
  7. 執行完畢后,調用棧Stack為空;
  8. 重復第3-7個步驟;
  9. 重復第3-7個步驟;
    ......

看到了吧,結論是什么?結論是:微隊列全部執行完,才執行宏隊列的第一個任務,執行完宏隊列的第一個任務之后,又去查看微隊列是否有任務,如果有,則全部執行,然后再看宏隊列的第一個任務。(宏隊列說:臥槽,歧視我?)

結合到現在的例子,new Promise()的內容是同步代碼,.then()是異步的,而且是微隊列的,優先級高,setTimeout屬于宏隊列的,優先級低。.then方法的回調里面的同步任務,優先級肯定比new Promise()外面的setTimeout任務的優先級;而.then方法的回調里面的setTimeout任務,在宏隊列里面沒有排第一,所以優先級比new Promise()外面的setTimeout任務的優先級,因為外面的setTimeout任務在宏隊列里排第一。

所以,從p對象賦值語句開始,JS引擎的執行順序是:

new Promise()內的同步任務
-> 外部下方的同步任務
-> .then鏈里的所有回調里的同步任務
-> 外部的宏隊列任務的回調,加上new Promise()內的宏隊列任務的回調,按回調時間依次執行,如果時間一致,按照書寫順序定
-> .then鏈里的所有回調里的回調任務

由此可以看出,如果在then鏈條之外還寫代碼的話,優先級會比較混亂,就像本文開頭說的,在then鏈條外面寫代碼不是一個好主意,應該由new Promise()和then鏈條統一管理本次需要執行的所有代碼,否則優先級不容易把控。

Promise.prototype.catch()

.catch()是什么?它是.then()的一個子集,也就是說專用于接收promise對象的reject()傳過來的error參數的。其他沒什么特別的。也就是說,.then()可以有兩個回調函數,.catch()只有一個回調函數。永遠盡量在能用.catch()的場合全用.catch()

.catch()可以鏈式調用么?

可以。

可以跟.then()混合鏈式么?

可以。

如果上一層的.then()沒有reject,.catch()會執行嗎?

不會,會被JS引擎跳過。

.catch()下一層如果是.then(),會執行嗎?

會。

參數怎么傳遞?

這個then接收的參數是上一個then傳遞的值,跟上一層的catch無關。也就是說,引擎跳過不執行的代碼,該怎么傳遞就怎么傳遞。

連寫.catch().then(//只寫一個回調)是并列關系么?

絕對不是,是執行的前后關系。從來沒有什么并列關系,只有連續的或者跳躍的前后關系。連寫.then(//只寫一個回調).catch()也不是并列關系。

Promise.all()

Promise.all方法用于將多個promise實例,包裝成一個新的promise實例。

Promise.all()方法接受一個數組作為參數,p1、p2、p3都是Promise對象的實例,如果不是,就會先調用下面講到的Promise.resolve方法,將參數轉為Promise實例,再進一步處理。(Promise.all()方法的參數可以不是數組,但必須具有Iterator接口,且返回的每個成員都是Promise實例。)

p的狀態由p1、p2、p3決定,分成兩種情況。

  1. 只有p1、p2、p3的狀態都變成fulfilled,p的狀態才會變成fulfilled,此時p1、p2、p3的返回值組成一個數組,傳遞給p的回調函數。
    注意,如果p1的最后一個then的回調沒有return命令,那么p1的返回值就是undefined,即使倒數第二個then的回調有返回值也沒有用,只看最后一個then的回調的返回值。
    再注意,組成的數組的順序是按照.all()參數的書寫順序而定,跟誰先返回值無關。
  2. 只要p1、p2、p3之中有一個被rejected,p的狀態就變成rejected,此時第一個被reject的實例的返回值,會傳遞給p的回調函數。

下面是一個具體的例子。

var p1 = new Promise(function(resolve, reject) {
    setTimeout(function() {
        console.log('第一個回調');
        reject(3);
    }, 3000);
});

var p2 = new Promise(function(resolve, reject) {
    setTimeout(function() {
        console.log('第二個回調');
        reject(2);
    }, 2000);
});

Promise.all([p1, p2]).catch(function(value) {
    return new Promise(function(resolve, reject) {
        setTimeout(function() {
            console.log('第三個回調');
            resolve(value * 2);
            console.log(value * 2);
        }, 2000);
    });
}).then(function(value) {
    setTimeout(function() {
        console.log('第四個回調');
        console.log(value * 2);
    }, 1000);
});

結果是下圖。由于p1和p2都rejected,所以catch捕獲的參數是p2傳過來的,因為p2的延遲比p1短。

Paste_Image.png

如果p1的回調是同步任務,p2是異步任務,毫無疑問,catch捕獲的參數會是p1傳過來的,但是,通常肯定不這么用,這么寫太蠢了。如果p1和p2都是同步任務?更蠢,那么你干嘛不把p1和p2寫到一起呢?而且,根本不需要Promises寫法。

如果p1和p2都fulfilled,那么value是一個數組。不代碼舉例了。

總結:Promise.all()方法的適用場合,是多個異步任務并發執行,在最后一個任務成功完成之后,給出一個回調。比如,并發10個xhr線程,傳10個文件,你并不知道哪個文件會先傳完,Promise.all()方法能確保在10個文件都傳完的那一刻給出完成提示。

如果不用Promise.all()方法,通常做法是設一個計數器初始值為0,每上傳成功一個文件就+1,然后判斷一次,看看計數器是否等于10,如果等于的話,就給出完成提示。最終相當于判斷10次。

Promise.race()

Promise.race方法同樣是將多個Promise實例,包裝成一個新的Promise實例。

var p = Promise.race([p1,p2,p3]);

上面代碼中,只要p1、p2、p3之中有一個實例率先改變狀態,p的狀態就跟著改變。那個率先改變的Promise實例的返回值,就傳遞給p的回調函數。

注意,所謂率先改變狀態,可能會改成fulfilled,也可能會改成rejected,都可以。

Promise.race方法的參數與Promise.all方法一樣,如果不是Promise實例,就會先調用下面講到的Promise.resolve方法,將參數轉為Promise實例,再進一步處理。

Promise.race的使用場合不算常見,比如一款小游戲,三輛賽車比賽,任何一個車先到達終點,比賽就結束,那么可以適用于Promise.race。或者是一個躲開障礙的游戲,任何一個障礙物撞到你,游戲就結束,那么可以適用于Promise.race。

還一個場合是超時判定。比如一個ajax請求,30秒下載不下來就算失敗。這樣,p1是ajax請求,p2是30秒計數器,誰先完成,p的狀態就隨誰。那個率先改變的Promise實例的返回值,就傳遞給p的回調函數。

Promise.resolve()

有時需要將現有對象轉為Promise對象,Promise.resolve方法就起到這個作用。

var jsPromise = Promise.resolve($.ajax('/whatever.json'));

上面代碼將jQuery生成的deferred對象,轉為一個新的Promise對象。

Promise.resolve方法的參數分成四種情況。

(1)參數是一個Promise實例

如果參數是Promise實例,那么Promise.resolve將不做任何修改、原封不動地返回這個實例。

(2)參數是一個thenable對象
thenable對象指的是具有then方法的對象,比如下面這個對象。

var thenable = {
    then: function(resolve, reject) {
        resolve(42);
    }
};

var jsPromise = Promise.resolve(thenable);

jsPromise.then(function(value) {
    console.log(value);  // 42
});

(3)參數不是具有then方法的對象,或根本就不是對象
如果參數是一個原始值,或者是一個不具有then方法的對象,則Promise.resolve方法返回一個新的Promise對象,狀態為Resolved,實例p向then的回調函數傳的參數就是那個原始值。

var p = Promise.resolve('Hello');

p.then(function (s){
      console.log(s) // Hello
});

上面代碼生成一個新的Promise對象的實例p。由于字符串Hello不屬于異步操作(判斷方法是它不是具有then方法的對象),返回Promise實例的狀態從一生成就是Resolved,所以回調函數會立即執行。Promise.resolve()方法的參數,會同時傳給回調函數。

(4)不帶有任何參數
Promise.resolve()方法允許調用時不帶參數,直接返回一個Resolved狀態的Promise對象。實例p向then的回調函數傳的參數是undefined

所以,如果希望得到一個Promise對象,比較方便的方法就是直接調用Promise.resolve()方法。

var p = Promise.resolve();

p.then(function (s){
  console.log(1)
});

console.log(2);

上面代碼的變量p就是一個Promise對象。

Promise.reject()

Promise.reject()方法也會返回一個新的promise實例,該實例的狀態為rejected
。它的參數用法與Promise.resolve()方法完全一致。

最佳實踐

現在Promises規范全部介紹完了。然后就是最佳實踐。

參考文檔:

promises 很酷,但很多人并沒有理解就在用了

打開Promise的正確姿勢

本文作者就是我,簡書的microkof。如果您覺得本文對您的工作有意義,產生了不可估量的價值,那么請您不吝打賞我,謝謝!

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

推薦閱讀更多精彩內容

  • 本文適用的讀者 本文寫給有一定Promise使用經驗的人,如果你還沒有使用過Promise,這篇文章可能不適合你,...
    HZ充電大喵閱讀 7,322評論 6 19
  • 你不知道JS:異步 第三章:Promises 在第二章,我們指出了采用回調來表達異步和管理并發時的兩種主要不足:缺...
    purple_force閱讀 2,102評論 0 4
  • Promiese 簡單說就是一個容器,里面保存著某個未來才會結束的事件(通常是一個異步操作)的結果,語法上說,Pr...
    雨飛飛雨閱讀 3,367評論 0 19
  • 00、前言Promise 是異步編程的一種解決方案,比傳統的解決方案——回調函數和事件——更合理和更強大。它由社區...
    夜幕小草閱讀 2,137評論 0 12
  • 青磚黛瓦不解憂,緩帶輕裘只影走。忍淚回首前塵秋。 寒山幾度月如鉤,紅箋小字不訴愁。縵紗倩影上心頭。
    此去昔年入夢閱讀 420評論 2 2