Promise進(jìn)階

目前來(lái)看,異步操作的未來(lái)非async/await(ES7)莫屬。但是大多數(shù)項(xiàng)目中激挪,還不能立刻扔掉歷史包袱,而且Promise其實(shí)也是實(shí)現(xiàn)async/await的基礎(chǔ)锋叨,在ES6中Promise也被寫(xiě)入了規(guī)范中垄分,所以深入學(xué)習(xí)一下Promise還是很有必要的。

首先拋開(kāi)Promise娃磺,了解一下異步操作的流程薄湿。

假設(shè)有一個(gè)異步任務(wù)的模板,我們使用setTimeout模擬異步豌鸡,在每個(gè)異步任務(wù)中先打印下這個(gè)參數(shù)arg嘿般,然后以2*arg作為參數(shù)傳入回調(diào)函數(shù)中。下面我們分別以串行涯冠、并行方式執(zhí)行幾個(gè)異步任務(wù)炉奴。

asyncFn=(arg,cb)=>{
    setTimeout(function(){
        console.log(`參數(shù)為${arg}`)
        cb(arg*2)
    },1000);
}
//這是要執(zhí)行異步任務(wù)的參數(shù)隊(duì)列
let items=[1,2,3,4,5,6];

串行任務(wù):在每個(gè)異步任務(wù)的回調(diào)方法中通過(guò)shift()方法,每次從任務(wù)隊(duì)列中取出一個(gè)值蛇更,并且更新剩余任務(wù)的數(shù)組瞻赶,實(shí)現(xiàn)任務(wù)的接力進(jìn)行。

let results=[];
final=(value)=>{
    console.log(`完成:${value}`);
}

series=(item)=>{
    if (item) {
        asyncFn(item,function(res){
            results.push(res);;
            return series(items.shift());
        })
    }else{
        final(results);
        console.timeEnd('sync');
    }
}
console.time('sync');
series(items.shift());
//串行執(zhí)行6.10s

并行任務(wù):一開(kāi)始就將所有任務(wù)都執(zhí)行派任,然后監(jiān)測(cè)負(fù)責(zé)保存異步任務(wù)執(zhí)行結(jié)果的數(shù)組的長(zhǎng)度砸逊,若等于任務(wù)隊(duì)列長(zhǎng)度時(shí),則是所有異步任務(wù)都執(zhí)行完畢掌逛。

let len=items.length;

console.time('asyncFn');
items.forEach((item)=>{
    asyncFn(item,function(res){
        results.push(res);
        if (results.length===len) {
            final(results);
            console.timeEnd('asyncFn');
        }
    })
})
//并行執(zhí)行1.01s

對(duì)并行任務(wù)的數(shù)量進(jìn)行控制:增加一個(gè)參數(shù)記錄正在執(zhí)行的任務(wù)的個(gè)數(shù)师逸,每開(kāi)始執(zhí)行一個(gè)任務(wù)加1,每到回調(diào)函數(shù)即將結(jié)束時(shí)減1豆混。

//并行與串行的配合篓像,即設(shè)定每次最多能并行n個(gè)異步任務(wù)
let running=0;
let limit=2;
console.time('control');
launcher=()=>{
    while(running < limit && items.length>0){
        let item = items.shift();
        running++;
        asyncFn(item,function(res){
            results.push(res);
            running--;
            if (items.length>0) {
                launcher();
            }else if(running==0){
                final();
                console.timeEnd('control');
            }
        });
    }
}
launcher();
//3.01s

Promise基礎(chǔ)回顧

then方法可以鏈?zhǔn)秸{(diào)用

(new Promise(fn1))
.then(step1)
.then(step2)
.then(step3)
.then(
    console.log
    console.error
    )

錯(cuò)誤具有傳遞性。console.error可以顯示之前任一步發(fā)生的錯(cuò)誤皿伺,而且該步之后的任務(wù)不會(huì)繼續(xù)執(zhí)行员辩。但是console.log只能顯示step3的返回值。

新建一個(gè)promise對(duì)象

var promise=new Promise((resolve,reject){})

實(shí)例方法

promise.then(onFullfilled,onRejected)

靜態(tài)方法

Promise.resolve()
Promise.reject()
  • Promise.resolve

    將傳遞給他的參數(shù)填充到Promise對(duì)象并返回這個(gè)Promise對(duì)象鸵鸥。

    Promise.resolve(42) 可以被認(rèn)為是

     new Promise(function(resolve){
      resolve(42)
    })
    

    的語(yǔ)法糖奠滑。

    Promise.resolve() 方法還能將 thenable 對(duì)象轉(zhuǎn)換為ES6中定義的promise對(duì)象。

    thenable 對(duì)象就是具有then方法但不是promise對(duì)象的對(duì)象,比如jQuery.ajax()的返回對(duì)象

    即使一個(gè)對(duì)象具有 .then 方法宋税,也不一定就能作為ES6 Promises對(duì)象使用

    var promise=Promise.resolve($.ajax('/json/comment.json'));
    promise.then(function(value){
      console.log(value);
    })
    
  • Promise.reject

    與Promise.resolve類(lèi)似的靜態(tài)方法

    Promise.reject(new Error('err'))摊崭;
    

    等同于

 new Promise(function(resolve,reject){
   reject(new Error('err'))
})

常見(jiàn)應(yīng)用

//使用Promise封裝一個(gè)ajax請(qǐng)求:
function getURL(url){
    return new Promise(function(resolve,reject){
        var req=new XMLHttpRequest();
        req.open('GET',url,true);
        req.onload=function(){
            if (req.status==200) {
                resolve(req.responseText);
            }else{
                reject(new Error(req.statusText))
            }
        };
        req.onerror=function(){
            reject(new Error(req.statusText));
        };
        res.send();
    })
}

//異步加載圖片
let preloadImage=(path)=>{
    return new Promise(function(resolve,reject){
        let img=new Image();
        img.onload=resolve;
        img.onerror=reject;
        img.src=path;
    })
}

錯(cuò)誤捕獲: catch與then

catch方法只是then(undefined,onReject)的封裝,實(shí)質(zhì)是一樣的弃甥。

promise.then(undefined,function(err){
  console.error(err);
})
  • 使用promise.then(onFulfilled, onRejected) 的話onFulFilled中發(fā)生錯(cuò)誤無(wú)法捕獲
  • 使用.catch鏈?zhǔn)皆趖hen后調(diào)用可以捕獲then中的錯(cuò)誤
  • 本質(zhì)上一樣爽室,區(qū)別使用場(chǎng)合
錯(cuò)誤捕獲在IE8的問(wèn)題

catch是ES3中的保留字,所以在IE8以下不能作為對(duì)象的屬性名使用淆攻,會(huì)出現(xiàn)identifier not found錯(cuò)誤。

  • 點(diǎn)標(biāo)記法要求對(duì)象的屬性必須是有效的標(biāo)識(shí)符
  • 中括號(hào)標(biāo)記法可以將非法標(biāo)識(shí)符作為對(duì)象的屬性名使用
var promise=Promise.reject(new Error('msg'));
promise["catch"](function(error){
    console.error(error);
})

或者使用then方法中添加第二個(gè)參數(shù)來(lái)避免這個(gè)問(wèn)題
then(undefined,onReject)

Promise的同步異步

Promise只能使用異步調(diào)用方式嘿架。

//Promise在定義時(shí)就會(huì)調(diào)用
var promise=new Promise(function(resolve){
    resolve(2);//異步調(diào)用回調(diào)函數(shù)
    console.log('innner')
})
promise.then(function(value){
    console.log(value);
})
console.log('outer');

會(huì)依次打印inner,outer,2

  • 決不能對(duì)異步函數(shù)進(jìn)行同步調(diào)用瓶珊,處理順序可能會(huì)與語(yǔ)氣不符,可能帶來(lái)意料之外的后果
  • 還可能導(dǎo)致棧溢出或者異常處理錯(cuò)誤等耸彪。

Promise保證了每次調(diào)用都是異步的伞芹,所以在實(shí)際編碼中不需要使用setTimeout自己實(shí)現(xiàn)異步。

有多個(gè)Promise實(shí)例時(shí):
  • promise.all() 所有異步任務(wù)并行執(zhí)行

    接受promise對(duì)象組成的數(shù)組作為參數(shù)蝉娜。輸出的每個(gè)promise的結(jié)果和參數(shù)數(shù)組的順序一致唱较。

  • promise.race 有一個(gè)異步任務(wù)完成則返回結(jié)果

    promise.race()同樣接受多個(gè)promise對(duì)象組成的數(shù)組作為參數(shù),但是只要有一個(gè)promise對(duì)象變?yōu)閒ulFilled或者rejected狀態(tài)召川,就會(huì)繼續(xù)后面的處理

基于promise.race()實(shí)現(xiàn)超時(shí)處理

function delayPromise(ms) {
    return new Promise(function(resolve) {
        setTimeout(resolve, ms);
    })
}

function timeoutPromise(promise, ms) {
  //用以提供超時(shí)基準(zhǔn)的promise實(shí)例
    var timeout = delayPromise(ms).then(function() {
        throw new Error(`operation timed out after ${ms} ms`);
    })
    return Promise.race([promise, timeout]);
}

//新的task
var taskPromise = new Promise(function(resolve) {
    var delay = Math.random() * 2000;
    setTimeout(function() {
        resolve(`${dealy} ms`);
    }, dealy)
});

timeoutPromise(taskPromise, 1000)
    .then(function(value) {
        console.log(`task在規(guī)定時(shí)間內(nèi)結(jié)束${value}`)
    })
    .catch(function(err) {
        console.log(`發(fā)生超時(shí):${err}`);
    })

但是不能區(qū)分這個(gè)異常是普通錯(cuò)誤還是超時(shí)錯(cuò)誤南缓。需要定制一個(gè)error對(duì)象。

function copyOwnFrom(target,source){
    Object.getOwnPropertyNames(source).forEach(function(propName){
        Object.defineProperty(target,propName,Object.getOwnPropertyDescriptor(source,propName));
    })
    return target
}
//通ECMAScript提供的內(nèi)建對(duì)象Error實(shí)現(xiàn)繼承
function TimeoutError(){
    var superInstance=Error.apply(null,arguments);
    copyOwnFrom(this,superInstance);
}

TimeoutError.prototype=Object.create(Error.prototype);
TimeoutError.prototype.constructor=TimeoutError;

用于提供超時(shí)基準(zhǔn)的promise實(shí)例改為

var timeout = delayPromise(ms).then(function() {
        throw new TimeoutError(`operation timed out after ${ms} ms`);
    })

在錯(cuò)誤捕獲中可修改為:

timeoutPromise(taskPromise, 1000)
    .then(function(value) {
        console.log(`task在規(guī)定時(shí)間內(nèi)結(jié)束${value}`)
    })
    .catch(function(err) {
        if(err instanceof TimeoutError){
             console.log(`發(fā)生超時(shí):${err}`);
        }else{
             console.log(`錯(cuò)誤:${err}`);
        }
    })

超時(shí)取消XHR請(qǐng)求

//通過(guò)cancelableXHR 方法取得包裝了XHR的promise對(duì)象和取消該XHR請(qǐng)求的方法
//
function cancelableXHR(url){
    var req=new XMLHttpRequest();
    var promise=new Promise(function(resolve,reject){
        req.open('GET',url,true);
        req.onload=function(){
            if (req.status===200) {
                resolve(req.responseText);
            }else{
                reject(new Error(req.statusText))
            }
        }
        req.onerror=function(){
            reject(new Error(req.responseText))
        }
        req.onabort=function(){
            reject(new Error('abort this request'))
        }
        res.send();
    })
    var abort=function(){
        if (req.readyState!==XMLHttpRequest.UNSENT) {
            req.abort();
        }
    }

    return {
        promise:promise,
        abort:abort
    }
}

var object=cancelableXHR('http://www.sqqs.com/index')

timeoutPromise(object.promise,1000).then(function(content){
    console.log(`content:${content}`);
}).catch(function(error){
    if (error instanceof TimeoutError) {
        object.abort();
        return console.log(error)
    }
    console.log(`XHR Error:${error}`);
})

promise 順序處理sequence

promise.all()是多個(gè)promise對(duì)象同時(shí)執(zhí)行荧呐,沒(méi)有api直接支持多個(gè)任務(wù)線性執(zhí)行汉形。

我們需要在上一個(gè)任務(wù)執(zhí)行結(jié)果的promise對(duì)象的基礎(chǔ)上執(zhí)行下一個(gè)promise任務(wù)。

var promiseA = function() {
    return new Promise(function(resolve) {
        setTimeout(function() {
            resolve(111);
        }, 200)

    })
}

var promiseB = function(args) {
    return new Promise(function(resolve) {
        setTimeout(function() {
            resolve(2222);
            console.timeEnd('sync');
        }, 200);
    })
}
console.time('sync');
var result = Promise.resolve();
[promiseA, promiseB].forEach(function(promise) {
    result = result.then(promise)
})
//print
//sync:408ms

通過(guò)這個(gè)名為result的promise對(duì)象來(lái)不斷更新保存新返回的promise對(duì)象倍阐,從而實(shí)現(xiàn)一種鏈?zhǔn)秸{(diào)用概疆。

也可以使用reduce重寫(xiě)循環(huán),使得代碼更加美觀一些:

console.time('sync');
tasks=[promiseA, promiseB];
tasks.reduce(function(result,promise){
    return result.then(promise)
},Promise.resolve())

其中Promise.resolve()作為reduce方法的初始值賦值給result峰搪。

promise穿透--永遠(yuǎn)往then中傳遞函數(shù)

如下例子岔冀,在then中傳遞了一個(gè)promise實(shí)例

Promise.resolve('foo').then(Promise.resolve('bar')).then(function(result){
    console.log(result)
})

打印結(jié)果為foo,像then 中傳遞的并非一個(gè)函數(shù),實(shí)際上會(huì)將其解釋為then(null)概耻。若想要得到bar,需要將then中傳遞一個(gè)函數(shù)

Promise.resolve('foo').then(function() {
    return Promise.resolve('bar')
}).then(function(result) {
    console.log(result)
})
//print result:
//bar

如果在then中的函數(shù)沒(méi)有對(duì)promise對(duì)象使用return返回呢使套,又是什么結(jié)果?

Promise.resolve('foo').then(function() {
    Promise.resolve('bar')
}).then(function(result) {
    console.log(result)
})

會(huì)返回一個(gè)undefined咐蚯。

拋磚引玉童漩,我們?cè)倏偨Y(jié)一下向then中傳遞函數(shù)的情況

var doSomething=function(){
    return Promise.resolve('bar')
}
var printResult=function(result){
    console.log(`result:${result}`)
}
//試想一下,以下幾個(gè)例子輸出的結(jié)果分別是什么
Promise.resolve('foo').then(function(value){
    return doSomething();
}).then(printResult)

Promise.resolve('foo').then(function(){
    doSomething();
}).then(printResult)

Promise.resolve('foo').then(doSomething()).then(printResult)

Promise.resolve('foo').then(doSomething).then(printResult)

【參考資料】

promise中需要注意的問(wèn)題

promise 迷你書(shū)

js原生promise

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末春锋,一起剝皮案震驚了整個(gè)濱河市矫膨,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖侧馅,帶你破解...
    沈念sama閱讀 216,496評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件危尿,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡馁痴,警方通過(guò)查閱死者的電腦和手機(jī)谊娇,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,407評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)罗晕,“玉大人济欢,你說(shuō)我怎么就攤上這事⌒≡ǎ” “怎么了法褥?”我有些...
    開(kāi)封第一講書(shū)人閱讀 162,632評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)酬屉。 經(jīng)常有香客問(wèn)我半等,道長(zhǎng),這世上最難降的妖魔是什么呐萨? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,180評(píng)論 1 292
  • 正文 為了忘掉前任杀饵,我火速辦了婚禮,結(jié)果婚禮上谬擦,老公的妹妹穿的比我還像新娘切距。我一直安慰自己,他們只是感情好怯屉,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,198評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布蔚舀。 她就那樣靜靜地躺著,像睡著了一般锨络。 火紅的嫁衣襯著肌膚如雪赌躺。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,165評(píng)論 1 299
  • 那天羡儿,我揣著相機(jī)與錄音礼患,去河邊找鬼。 笑死掠归,一個(gè)胖子當(dāng)著我的面吹牛缅叠,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播虏冻,決...
    沈念sama閱讀 40,052評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼肤粱,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了厨相?” 一聲冷哼從身側(cè)響起领曼,我...
    開(kāi)封第一講書(shū)人閱讀 38,910評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤鸥鹉,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后庶骄,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體毁渗,經(jīng)...
    沈念sama閱讀 45,324評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,542評(píng)論 2 332
  • 正文 我和宋清朗相戀三年单刁,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了灸异。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,711評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡羔飞,死狀恐怖肺樟,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情褥傍,我是刑警寧澤儡嘶,帶...
    沈念sama閱讀 35,424評(píng)論 5 343
  • 正文 年R本政府宣布,位于F島的核電站恍风,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏誓篱。R本人自食惡果不足惜朋贬,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,017評(píng)論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望窜骄。 院中可真熱鬧锦募,春花似錦、人聲如沸邻遏。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,668評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)准验。三九已至赎线,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間糊饱,已是汗流浹背垂寥。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,823評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留另锋,地道東北人滞项。 一個(gè)月前我還...
    沈念sama閱讀 47,722評(píng)論 2 368
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像夭坪,于是被迫代替她去往敵國(guó)和親文判。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,611評(píng)論 2 353

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

  • 官方中文版原文鏈接 感謝社區(qū)中各位的大力支持室梅,譯者再次奉上一點(diǎn)點(diǎn)福利:阿里云產(chǎn)品券戏仓,享受所有官網(wǎng)優(yōu)惠疚宇,并抽取幸運(yùn)大...
    HetfieldJoe閱讀 11,026評(píng)論 26 95
  • 00灰嫉、前言Promise 是異步編程的一種解決方案,比傳統(tǒng)的解決方案——回調(diào)函數(shù)和事件——更合理和更強(qiáng)大嗓奢。它由社區(qū)...
    夜幕小草閱讀 2,133評(píng)論 0 12
  • Promiese 簡(jiǎn)單說(shuō)就是一個(gè)容器讼撒,里面保存著某個(gè)未來(lái)才會(huì)結(jié)束的事件(通常是一個(gè)異步操作)的結(jié)果,語(yǔ)法上說(shuō)股耽,Pr...
    雨飛飛雨閱讀 3,358評(píng)論 0 19
  • Promise的含義: ??Promise是異步編程的一種解決方案根盒,比傳統(tǒng)的解決方案——回調(diào)函數(shù)和事件——更合理和...
    呼呼哥閱讀 2,170評(píng)論 0 16
  • 小學(xué)同學(xué)群,剛開(kāi)始創(chuàng)建物蝙,是依稀的幾個(gè)人炎滞,還是幾個(gè)男同學(xué),后來(lái)進(jìn)入個(gè)女同學(xué)诬乞,也是沒(méi)見(jiàn)出來(lái)吭過(guò)聲册赛,于是大家就把她忽略了...
    你的珍貴閱讀 1,315評(píng)論 21 16