JavaScript 之 Promise

開始之前

JavaScript是單線程語(yǔ)言,所以都是同步執(zhí)行的刀森,要實(shí)現(xiàn)異步就得通過回調(diào)函數(shù)的方式,但是過多的回調(diào)會(huì)導(dǎo)致回調(diào)地獄报账,代碼既不美觀研底,也不易維護(hù)埠偿,所以就有了promise;Promise 是異步編程的一種解決方案,比傳統(tǒng)的解決方案——回調(diào)函數(shù)和事件——更合理和更強(qiáng)大榜晦。它由社區(qū)最早提出和實(shí)現(xiàn)冠蒋,ES6 將其寫進(jìn)了語(yǔ)言標(biāo)準(zhǔn),統(tǒng)一了用法乾胶,原生提供了 Promise 對(duì)象抖剿。Promise 是一個(gè)擁有 then 方法的對(duì)象或函數(shù)。

Promise

問題探討

下面通過多個(gè)示例來(lái)感受一下不使用 promise 時(shí)识窿,處理相應(yīng)問題的不易斩郎,及生成了不便閱讀的代碼。

定時(shí)嵌套

假設(shè)我們想要實(shí)現(xiàn)網(wǎng)頁(yè)上一個(gè)盒子先向右邊移動(dòng)喻频,移動(dòng)到一定距離后逐漸縮小缩宜,縮小到一定大小后停止縮小。下面我們用兩個(gè)定時(shí)器去實(shí)現(xiàn)甥温,一個(gè)定時(shí)器控制移動(dòng)锻煌,當(dāng)該定時(shí)器結(jié)束后啟動(dòng)另一個(gè)定時(shí)器控制縮小。

<!DOCTYPE html>
<html>
<head>
    <title>JavaScript</title>
    <style type="text/css">
        #box{
            height: 100px;
            width: 100px;
            background-color: #3498db;
            position: absolute;
        }
    </style>
</head>
<body>
    <div id="box"></div>
    <script type="text/javascript">
        function interval(callback,delay=100){
            let id = setInterval(()=>callback(id),delay)
        }
        const box = document.getElementById('box');
        interval((timeId)=>{
            const left = Number.parseInt(window.getComputedStyle(box).left);
            box.style.left = left + 10 + 'px';
            if(left > 200){
                clearInterval(timeId);
                interval((timeId)=>{
                    const width = Number.parseInt(window.getComputedStyle(box).width);
                    const height = Number.parseInt(window.getComputedStyle(box).height);
                    box.style.width = width - 5 + 'px';
                    box.style.height = height - 5 + 'px';
                    if(width <= 50){
                        clearInterval(timeId)
                    }
                })
            }
        });
    </script>
</body>
</html>

圖片加載

圖片加載完成后設(shè)置一個(gè)圖片邊框

<!DOCTYPE html>
<html>
<head>
    <title>JavaScript</title>
</head>
<body>
    <script type="text/javascript">
        function loadImg(file,resolve,reject){
            const img = new Image();
            img.src = file;
            img.onload = () => {
                resolve(img);
            };
            img.onerror = () => {
                reject(new Error('load fail'))
            }
            document.body.appendChild(img);
        }
        loadImg('https://static.collectui.com/shots/4418237/health-landing-page-large',
            image => {
                console.log('圖片加載成功,開始修改邊框');
                image.style.border = "5px solid #3498db";
            },
            error => {
                console.log(error);
            }
        )
    </script>
</body>
</html>

疫情解除窿侈,員工復(fù)工通告

<!DOCTYPE html>
<html>
<head>
    <title>JavaScript</title>
</head>
<body>
    <script type="text/javascript">
        function notice(msg,then){
            then(msg);
        }
        function work(){
            notice('全國(guó)上下正在積極應(yīng)對(duì)疫情變化', msg=>{
                console.log(msg);
                notice('疫情已經(jīng)結(jié)束炼幔,請(qǐng)各級(jí)政府通知企業(yè)復(fù)工', msg => {
                    console.log(`收到黨中央,國(guó)務(wù)院消息:${msg}`);
                    setTimeout(()=>{
                        notice('請(qǐng)各企業(yè)有序復(fù)工', msg => {
                            console.log(`收到政府消息:${msg}`);
                            notice('請(qǐng)員工在規(guī)定時(shí)間內(nèi)到到崗上班史简,但仍需注意防護(hù)', msg => {
                                console.log(`收到企業(yè)消息:${msg}`);
                                setTimeout(()=>{
                                    notice('員工已到崗',msg => {
                                        console.log(`收到員工消息:${msg},通知結(jié)束`);
                                    })
                                },1000)
                            })
                        })
                    },1000)
                })
            })
        }
        work();
    </script>
</body>
</html>

上面的實(shí)例可以看出在沒有使用Promise的時(shí)候乃秀,一些相互關(guān)聯(lián)的異步操作就會(huì)一層層的嵌套,形成了回調(diào)地獄圆兵,而且對(duì)于閱讀和維護(hù)代碼帶來(lái)了極大的不便跺讯。

Promise 基本語(yǔ)法

Promise 初體驗(yàn)

在開始講解基本語(yǔ)法之前先改一下疫情復(fù)工通知的例子,感受一下promise帶來(lái)的便利殉农。

<!DOCTYPE html>
<html>
<head>
    <title>JavaScript</title>
</head>
<body>
    <script type="text/javascript">
        let country = new Promise((resolve,reject)=>{
            console.log('全國(guó)上下正在積極應(yīng)對(duì)疫情變化');
            resolve('疫情已經(jīng)結(jié)束刀脏,請(qǐng)各級(jí)政府通知企業(yè)復(fù)工');
        })
        let goverment = country.then(msg=>{
            console.log(`收到黨中央,國(guó)務(wù)院消息:${msg}`);
            return {
                then(resolve){
                    setTimeout(()=>{
                        resolve('請(qǐng)各企業(yè)有序復(fù)工');
                    },1000);
                }
            }
        })
        let enterment = goverment.then(msg=>{
            console.log(`收到政府消息:${msg}`);
            return{
                then(resolve){
                    setTimeout(()=>{
                        resolve('請(qǐng)員工在規(guī)定時(shí)間內(nèi)到崗上班超凳,但仍需注意防護(hù)')
                    },1000);
                }
            }
        })
        let employee = enterment.then(msg=>{
            console.log(`收到企業(yè)消息:${msg}`);
            return{
                then(resolve){
                    setTimeout(()=>{
                        resolve('員工已到崗');
                    },1000)
                }
            }
        })
        employee.then(msg=>{
            console.log(`收到員工消息:${msg},通知結(jié)束`);
        })
    </script>
</body>
</html>

使用promise后愈污,保證實(shí)現(xiàn)了原有功能,寫法上各級(jí)專注負(fù)責(zé)自己所管轄的內(nèi)容轮傍,避免了代碼的相互嵌套暂雹,使代碼變得更加容易閱讀,維護(hù)起來(lái)也更加方便创夜。

理解Promise

promise可以按照字面意思理解——承諾杭跪。那這次疫情來(lái)講。國(guó)家政府通知全民可以進(jìn)行正常的工作生活就是對(duì)人民的一種承諾。如果疫情控制住了涧尿,結(jié)束了就是成功系奉,我們就可以不用在家辦公或者隔離了。如果疫情沒有徹底控制住姑廉,就是拒絕缺亮,我們?nèi)院美^續(xù)現(xiàn)在的生活工作狀態(tài)。

  • 一個(gè)promise 必須有一個(gè)then的方法用于處理狀態(tài)和改變庄蹋。
    狀態(tài)說明:
    Promise包含pending fulfilled reject三種狀態(tài)
  • pending指初始等待狀態(tài)瞬内,初始化promise的狀態(tài)
  • resolve指已經(jīng)解決迷雪,將promise狀態(tài)設(shè)置為fulfilled
  • reject指拒絕處理限书,將promise狀態(tài)設(shè)置為rejected
  • promise是生產(chǎn)者,通過resolvereject函數(shù)告知結(jié)果
  • promise非常適合需要一定執(zhí)行時(shí)間的異步任務(wù)(一般網(wǎng)絡(luò)請(qǐng)求都是異步任務(wù))
  • 狀態(tài)量一旦改變就不可改改變
    promise是一種隊(duì)列狀態(tài)章咧,就像多米多骨牌倦西,狀態(tài)由上個(gè)傳遞到下一個(gè),然后一個(gè)個(gè)往下傳赁严,當(dāng)然其中任何一個(gè)promise也是可以改變狀態(tài)的扰柠。
    圖片2

    promise在沒有使用resolvereject更改狀態(tài)時(shí),狀態(tài)為pending
console.log(
    new Promise((resolve,reject)=>{});
) // Promise(<pending>)

promise使用ressolvereject改變狀態(tài)后

console.log(
  new Promise((resolve, reject) => {
    resolve("fulfilled");
  })
); //Promise {<resolved>: "fulfilled"}

console.log(
  new Promise((resolve, reject) => {
    reject("rejected");
  })
); //Promise {<rejected>: "rejected"}

promise創(chuàng)建任務(wù)時(shí)會(huì)立即執(zhí)行同步任務(wù)疼约,then會(huì)放在異步微任務(wù)中執(zhí)行卤档,需要等待同步任務(wù)執(zhí)行后才執(zhí)行。

let promise = new Promise((resolve,reject)=>{
  resolve('fulfilled');
  console.log('第一次打印');
});
promise.then(msg=>{
  console.log(msg);
});
console.log('第二次打印')

圖片3

上面的結(jié)果可以看出程剥,promise狀態(tài)改變后產(chǎn)生的微任務(wù)會(huì)在所有宏任務(wù)執(zhí)行后在執(zhí)行劝枣。

promise操作都是在其他代碼后執(zhí)行,下面會(huì)先輸出第一次輸出织鲸,之后才會(huì)彈出success

  • promisethen catch finally的方法都是異步任務(wù)
  • 程序需要將主任務(wù)執(zhí)行完才會(huì)執(zhí)行一步隊(duì)列任務(wù)
let promise  = new Promise((resolve,reject)=>{resolve('success')})
promise.then(alert);
alert('第一次輸出');
promise.then(()=>{
  alert('success之后彈出')
})

下面這個(gè)例子將在3秒后將promise狀態(tài)設(shè)置為fulfilled,然后執(zhí)行then方法

new Promise((resolve,reject)=>{
  setTimeout(()=>{
    resolve('fulfilled');
  },3000)
}).then(
  (msg)=>{
    console.log(msg);
  },
  (error)=>{
    console.log(error);
  }
)

狀態(tài)改變了就不能再修改了舔腾,下面先通過resolve改變?yōu)槌晒顟B(tài),表示promise狀態(tài)已經(jīng)完成搂擦,就不能用使用reject更改狀態(tài)了

new Promise((resolve, reject) => {
  resolve("操作成功");
  reject(new Error("請(qǐng)求失敗"));
}).then(
  msg => {
    console.log(msg);
  },
  error => {
    console.log(error);
  }
);

下面的例子中p2返回了p1的狀態(tài)已經(jīng)無(wú)意義了稳诚,后面then時(shí)對(duì)p1狀態(tài)的處理。即如果resolve參數(shù)是一個(gè)promise將會(huì)改變他的狀態(tài)瀑踢。

let p1 = new Promise((resolve,reject)=>{
  resolve('fulfilled');
  // reject('reject');
});
let p2 = new Promise((resolve,reject)=>{
    resolve(p1);
}).then(
    value=>{
        console.log(value);
    },
    reason=>{
        console.log(reason);
    }
);

當(dāng)promise作為參數(shù)傳遞時(shí)扳还,需要等待promise執(zhí)行完才可以繼承,下面的p2需要等待p1執(zhí)行完成橱夭。

  • 因?yàn)?code>p2 的 resolve 返回了 p1的promise,所以此時(shí)p2then方法已經(jīng)時(shí)p1的狀態(tài)了
  • 正因?yàn)橐陨显?code>then的第一個(gè)函數(shù)輸出了p1resolve的參數(shù)
let p1 = new Promise((resolve,reject)=>{
  setTimeout(()=>{
    resolve('操作成功');
  },2000)
})
let p2 = new Promise((resolve,reject)=>{
  resolve(p1);
})
p2.then(msg=>{
  console.log(msg);
}).catch(error=>{
  console.log(error)
})

then的使用

一個(gè)promise需要提供一個(gè)then方法來(lái)訪問promise的結(jié)果氨距,then相當(dāng)于定義當(dāng)promise狀態(tài)發(fā)生改變的處理,即promise處理異步操作徘钥,then用來(lái)處理結(jié)果衔蹲。

promise就像疫情中的真政府,then就是民眾,如果疫情被控制住了就分fulfilled, 如果沒有抑制舆驶,就reject橱健。那么民眾(then)就要處理不同的狀態(tài)。

  • then方法必須返回promise,用戶返回或者系統(tǒng)自動(dòng)返回沙廉。

  • 第一個(gè)函數(shù)在resolve狀態(tài)時(shí)執(zhí)行拘荡,即執(zhí)行resolve時(shí)執(zhí)行then的第一個(gè)函數(shù)處理成功的狀態(tài)

  • 第二個(gè)函數(shù)在reject狀態(tài)時(shí)執(zhí)行,即執(zhí)行reject時(shí)執(zhí)行then的第二個(gè)函數(shù)處理失敗狀態(tài)撬陵,該函數(shù)是可選的珊皿。

  • 兩個(gè)函數(shù)都接收promise傳出的值做為參數(shù)

  • 也可以使用catch來(lái)處理失敗的狀態(tài)(上面的最后一個(gè)例子)

  • 如果then返回promise,下一個(gè)then會(huì)在當(dāng)前promise狀態(tài)改變后繼續(xù)執(zhí)行

    語(yǔ)法說明

then的語(yǔ)法如下,onFulfilled函數(shù)處理fulfilled狀態(tài)巨税。onReject函數(shù)處理rejected狀態(tài)

  • onFulfilled 或 onRejected 不是函數(shù)將被忽略
  • 兩個(gè)函數(shù)只會(huì)被調(diào)用一次
  • onFulfilled在promise執(zhí)行成功時(shí)調(diào)用
  • onRejected在promise執(zhí)行拒絕時(shí)調(diào)用
promise.then(onFulfilled, onRejected)

基礎(chǔ)知識(shí)
then會(huì)在promise執(zhí)行完成后執(zhí)行蟋定,then第一個(gè)函數(shù)在resolve成功狀態(tài)執(zhí)行

let promise = new Promise((resolve, reject) => {
  resolve("success");
}).then(
  value => {
    console.log(`解決:${value}`);
  },
  reason => {
    console.log(`拒絕:${reason}`);
  }
);

then中第二個(gè)參數(shù)在失敗狀態(tài)執(zhí)行

let promise = new Promise((resolve, reject) => {
  reject("is error");
});
promise.then(
  msg => {
    console.log(`成功:${msg}`);
  },
  error => {
    console.log(`失敗:${error}`);
  }
);

如果只關(guān)心失敗時(shí)狀態(tài),then的第一個(gè)參數(shù)傳遞null

let promise = new Promise((resolve, reject) => {
  reject("is error");
});
promise.then(null, error => {
  console.log(`失敗:${error}`);
});

promise傳向then的值驶兜,如果then沒有可處理的函數(shù)远寸,會(huì)一直向后傳遞(現(xiàn)實(shí)中一般不會(huì)這么寫)

let p1 = new Promise((resolve, reject) => {
    reject("rejected");
})
.then()
.then(
  null,
  f => console.log(f)
);

下面這里例子 如果 onFulfilled不是函數(shù)且promise執(zhí)行成功,p2執(zhí)行成功并返回相同值

let promise = new Promise((resolve, reject) => {
  resolve("resolve");
});
let p2 = promise.then();
p2.then().then(resolve => {
  console.log(resolve);
});

如果 onRejected 不是函數(shù)且promise拒絕執(zhí)行驰后,p2 拒絕執(zhí)行并返回相同值

let promise = new Promise((resolve, reject) => {
  reject("reject");
});
let p2 = promise.then(() => {});
p2.then(null, null).then(null, reject => {
  console.log(reject);
});

鏈?zhǔn)秸{(diào)用

每次then都是一個(gè)全新的promise,默認(rèn)then返回的狀態(tài)是fulfilled

let promise = new Promise((resolve,reject)=>{
  resolve('fulfilled');
}).then(resolve=>{
  console.log(resolve);
}).then(resolve=>{
  console.log(resolve);
})

圖片3

每次的then都是一個(gè)全新的promise,不要以為上一個(gè)promise狀態(tài)會(huì)影響以后then返回的狀態(tài)

let p1 = new Promise(resolve=>{
  resolve();
});
let p2 = p1.then(()=>{
  console.log('這是對(duì)p1進(jìn)行處理')
});
p2.then(()=>{
  console.log('這是對(duì)p2進(jìn)行處理')
});
console.log(p1); // Promise {<resolve>}
console.log(p2); // Promise {<pending>}

# 再試試把上面兩行放在 setTimeout里
setTimeout(() => {
  console.log(p1); // Promise {<resolved>}
  console.log(p2); // Promise {<resolved>}
});

下面這個(gè)例子 then 是對(duì)上個(gè)promise 的rejected 的處理灶芝,每個(gè) then 會(huì)是一個(gè)新的promise郑原,默認(rèn)傳遞 fulfilled狀態(tài)

new Promise((resolve, reject) => {
  reject();
})
.then(
  resolve => console.log("fulfilled"),
  reject => console.log("rejected")
)
.then(
  resolve => console.log("fulfilled"),
  reject => console.log("rejected")
)
.then(
  resolve => console.log("fulfilled"),
  reject => console.log("rejected")
);
  
/*執(zhí)行結(jié)果*/
  rejected
  fulfilled
  fulfilled

在看下面一個(gè)例子

new Promise((resolve,reject)=>{
            reject('error')
        }).then(res=>console.log(res),error=>{console.log(error);})
        .then(res=>console.log(res),err=>console.log(err))
/*執(zhí)行結(jié)果*/
error
undifine

在看一個(gè)例子

new Promise((resolve,reject)=>{
            reject('error')
        }).then(res=>console.log(res),error=>{console.log(error);return 123})
        .then(res=>console.log(res),err=>console.log(err))
/*執(zhí)行結(jié)果*/
error
undifine

在上面我已經(jīng)介紹過在 promise實(shí)例中监署,如果狀態(tài)改變?yōu)閒ulfilled钠乏,則可以返回resolve函數(shù)晓避,然后做為參數(shù)傳到then中成功處理函數(shù)中,如果狀態(tài)為rejected暑塑,則可以傳到reject函數(shù)事格,然后作為參數(shù)傳遞到then中的拒絕函數(shù)中處理驹愚。而在then方法中是沒有JS部署好的resolve和reject函數(shù)的逢捺。再往上的第四個(gè)例子紅可以看到P2的狀態(tài)是pending的劫瞳。但是我們可以字then方法中通過設(shè)置返回值志于,創(chuàng)建新的Promise對(duì)象之后將返回值傳遞到下一個(gè)then方法中的成功或拒絕函數(shù)中去恨憎,如果沒有返回值則默認(rèn)為undefined。就是上面兩個(gè)例子所要表述的內(nèi)容。

如果內(nèi)部返回promise時(shí)钥组,將使用該promise

let p1 = new Promise(resolve=>{
    resolve();
});
let p2 = p1.then(()=>{
    return new Promise((resolve,reject)=>{
        resolve('內(nèi)部中的Promise');
    })
});
p2.then(res=>{
    console.log(res);
}); // 內(nèi)部中的Promise

如果 then 返回promise 時(shí),后面的then 就是對(duì)返回的 promise 的處理屿附,需要等待該 promise 變更狀態(tài)后執(zhí)行挺份。

let promise = new Promise(resolve => resolve());
let p1 = promise.then(() => {
  return new Promise(resolve => {
    setTimeout(() => {
      console.log(`p1`);
      resolve();
    }, 2000);
  });
}).then(() => {
  return new Promise((a, b) => {
    console.log(`p2`);
  });
});

Thenables

包含 then 方法的對(duì)象就是一個(gè) promise 匀泊,系統(tǒng)將傳遞 resolvePromise 與 rejectPromise 做為函數(shù)參數(shù)各聘。

文章開頭的地方使用Promise復(fù)寫的復(fù)工通知就是用的該方法躲因。

當(dāng)然類也可以

new Promise((resolve, reject) => {
  resolve(
    class {
      static then(resolve, reject) {
        setTimeout(() => {
          resolve("解決狀態(tài)");
        }, 2000);
      }
    }
  );
}).then(
  res => {
    console.log(`fulfilled: ${res}`);
  },
  err => {
    console.log(`rejected: ${err}`);
  }
);

如果對(duì)象中的 then 不是函數(shù)大脉,則將對(duì)象做為值傳遞

new Promise((resolve, reject) => {
  resolve();
})
.then(() => {
  return {
    then: "后盾人"
  };
})
.then(v => {
  console.log(v);
});

利用Promise用原聲JS封裝AJAX

function request(url){
            return new Promise((resolve,reject)=>{
                let XHR = XMLHttpRequest ? new XMLHttpRequest(): new window.ActiveXObject('Microsoft.XMLHTTP');
                XHR.onreadystatechange = funciton(){
                    if(XHR.readyState === 4){
                        if(XHR.status >=200 && XHR.status < 300 || XHR.status === 304){
                            try{
                                let response = JSON.parse(XHR.responseText);
                                resolve(response);
                            }catch(e){
                                reject(e);
                            }
                        }
                    }else{
                        reject(new Error('"Request was unsuccessful: " + XHR.statusText'));
                    }
                }
                XHR.open('GET', url, true);
        XHR.send(null);
            })
        }

catch

除了正常使用reject腺逛,Promise里面發(fā)生任何的異常棍矛,都會(huì)觸發(fā)失敗的狀態(tài)够委。

下面使用未定義的變量會(huì)觸發(fā)失敗狀態(tài)

let promise = new Promise((resolve, reject) => {
  abc;
}).then(
  value => console.log(value),
  reason => console.log(reason)
);

如果onFulfilledonReject拋出異常茁帽,則p2拒絕執(zhí)行并返回拒絕原因

let promise = new Promise((resolve,reject)=>{
    throw new Error('fail');
});
let p2 = promise.then();
p2.then().then(null,reject=>{
    console.log(reject);
})

catch用于失敗狀態(tài)的處理函數(shù)潘拨,等同于 then(null,reject){}

  • 建議使用catch處理錯(cuò)誤
  • catch放在最后面用于統(tǒng)一處理前面發(fā)生的錯(cuò)誤
const promise = new Promise((resolve, reject) => {
  reject(new Error("Notice: Promise Exception"));
}).catch(msg => {
  console.error(msg);
});

catch可以捕獲之前所有的promise的錯(cuò)誤铁追,所以將catch放在最后琅束。下面中例子中catch捕獲了第一個(gè)個(gè)then返回的promise的錯(cuò)誤涩禀。

new Promise((resolve, reject) => {
  resolve();
})
.then(() => {
  return new Promise((resolve, reject) => {
    reject(".then ");
  });
})
.then(() => {})
.catch(msg => {
  console.log(msg);
});

錯(cuò)誤是冒泡的操作的艾船,下面沒有任何一個(gè)then 定義第二個(gè)函數(shù)丽声,將一直冒泡到 catch 處理錯(cuò)誤

new Promise((resolve,reject)=>{
    reject(new Error('請(qǐng)求失敗'));
})
.then(msg=>{})
.then(msg=>{})
.catch(error=>{
    console.log(error)
})

catch 也可以捕獲對(duì) then 拋出的錯(cuò)誤處理

new Promise((resolve, reject) => {
  resolve();
})
.then(msg => {
  throw new Error("這是then 拋出的錯(cuò)誤");
})
.catch((error) => {
  console.log(error);
});

建議將錯(cuò)誤交給catch處理,而不是在 then中完成霉撵。

? 處理機(jī)制

promise中拋出的錯(cuò)誤也會(huì)在catch中捕獲徒坡,其實(shí)可以理解為在內(nèi)部自動(dòng)執(zhí)行了try...catch...

new Promise((resolve, reject) => {
  try {
    throw new Error("fail");
  } catch (error) {
    reject(error);
  }
}).catch(msg => {
  console.log(msg);
});

但像下面的在異步中 throw 將不會(huì)觸發(fā) catch喇完,而使用系統(tǒng)錯(cuò)誤處理

new Promise((resolve, reject) => {
  setTimeout(() => {
    throw new Error("fail");
  }, 2000);
}).catch(msg => {
  console.log(msg + "異步處理");
});

已經(jīng)處理過的將不會(huì)在catch重復(fù)處理

new Promise((resolve,reject)=>{
  reject('error')
}).then(null,error=>{
  console.log(error);
}).then()
  .catch(error=>{
  console.log(error)
})

catch 中發(fā)生的錯(cuò)誤也會(huì)拋給最近的錯(cuò)誤處理

new Promise((resolve, reject) => {
  reject();
})
.catch(msg => {
  throw new Error('error')
})
.then(null, error => {
  console.log(error);
});

? 定制錯(cuò)誤

可以根據(jù)不同的錯(cuò)誤類型進(jìn)行定制操作锦溪,下面將參數(shù)錯(cuò)誤與404錯(cuò)誤分別進(jìn)行了處理

<script type="text/javascript">
        class ParamError extends Error{
            constructor(message){
                super(message);
                this.name = 'ParamError'
            }
        }
        class HttpError extends Error{
            constructor(message){
                super(message);
                this.name = 'HttpError'
            }
        }
        function request(url){
            return new Promise((resolve,reject)=>{
                if(!/^http/.test(url)){
                    throw new ParamError('請(qǐng)求地址錯(cuò)誤');
                }
                let XHR = new XMLHttpRequest();
                XHR.onreadystatechange = funciton(){
                    if(XHR.readyState === 4){
                        if(XHR.status >=200 && XHR.status < 300 || XHR.status === 304){
                            try{
                                let response = JSON.parse(XHR.responseText);
                                resolve(response);
                            }catch(e){
                                reject(e);
                            }
                        }else if(XHR.statux === 404){
                            reject(new HttpError('網(wǎng)址不存在'))防楷;
                        }
                    }else{
                        reject(new Error('"Request was unsuccessful: " + XHR.statusText'));
                    }
                }
                XHR.open('GET',url,true);
                XHR.send(null);
            })
        }
        request('http://www.abc.com')
        .then(response=>{
            console.log(response)
        })
        .catch(error=>{
            if(error instanceof ParamError){
                console.log(error.message)
            }
            if(error instanceof HttpError){
                alert(error.message);
            }
            console.log(error)
        })
    </script>

finally

無(wú)論狀態(tài)是resolvereject 都會(huì)執(zhí)行此動(dòng)作复局,finally 與狀態(tài)無(wú)關(guān)亿昏。

const promise = new Promise((resolve, reject) => {
  reject("hdcms");
})
.then(msg => {
  console.log("resolve");
})
.catch(msg => {
  console.log("reject");
})
.finally(() => {
  console.log("resolve/reject狀態(tài)都會(huì)執(zhí)行");
});

下面使用 finally 處理加載狀態(tài),當(dāng)請(qǐng)求完成時(shí)移除加載圖標(biāo)。

request('http://localhost:8080')
.then(response=>{
  console.log(response)
})
.catch(error=>{
  console.log(error)
})
.finally(()=>{
  this.loading = false;
})

實(shí)例操作

下面是封裝的timeout 函數(shù)易迹,使用定時(shí)器操作更加方便

function timeout(times) {
  return new Promise(resolve => {
    setTimeout(resolve, times);
  });
}

timeout(3000)
  .then(() => {
    console.log("3秒后執(zhí)行");
    return timeout(1000);
  })
  .then(() => {
    console.log("執(zhí)行上一步的promise后1秒執(zhí)行");
  });

封閉 setInterval 定時(shí)器并實(shí)現(xiàn)動(dòng)畫效果

<body>
  <style>
    div {
      width: 100px;
      height: 100px;
      background: yellowgreen;
      position: absolute;
    }
  </style>
  <div></div>
</body>
<script>
  function interval(delay = 1000, callback) {
    return new Promise(resolve => {
      let id = setInterval(() => {
        callback(id, resolve);
      }, delay);
    });
  }
  interval(100, (id, resolve) => {
    const div = document.querySelector("div");
    let left = parseInt(window.getComputedStyle(div).left);
    div.style.left = left + 10 + "px";
    if (left >= 200) {
      clearInterval(id);
      resolve(div);
    }
  }).then(div => {
    interval(50, (id, resolve) => {
      let width = parseInt(window.getComputedStyle(div).width);
      div.style.width = width - 10 + "px";
      if (width <= 20) {
        clearInterval(id);
      }
    });
  });
</script>

接口擴(kuò)展

resolve

使用 promise.resolve 方法可以快速的返回一個(gè)promise對(duì)象

Promise.resolve('success').then(value=>{
    console.log(value);
})

下面將請(qǐng)求結(jié)果緩存,如果再次請(qǐng)求時(shí)直接返回帶值的 promise

<script type="text/javascript">
        const api = 'http://localhost:3000';
        function request(url){
            return new Promise((resolve,reject)=>{
                if(!/^http/.test(url)){
                    throw new ParamError('請(qǐng)求地址錯(cuò)誤');
                }
                let XHR = new XMLHttpRequest();
                XHR.onreadystatechange = function(){
                    if(XHR.readyState === 4){
                        if(XHR.status >=200 && XHR.status < 300 || XHR.status === 304){
                            try{
                                let response = JSON.parse(XHR.responseText);
                                resolve(response);
                            }catch(e){
                                reject(e);
                            }
                        }else if(XHR.status === 404){
                            reject(new HttpError('網(wǎng)址不存在'));
                        }else{
                            reject(new Error("Request was unsuccessful: " + XHR.statusText));
                        }
                    }
                }
                XHR.open('GET',url,true);
                XHR.send(null);
            })
        }
</script>
<script type="text/javascript">
        function queryAllNumbers(){
            const cache = queryAllNumbers.cache || (queryAllNumbers.chche = new Map());
            if(cache.has('numbers')){
                console.log('走緩存了');
                return Promise.resolve(cache.get('numbers'))
            }
            return request(`${api}/User/GetClassNumber`)
            .then(response=>{
                cache.set('numbers',response.Data);
                console.log('沒走緩存');
                return response.Data;
            })
        }
        queryAllNumbers()
        .then(response=>{
            console.log(response)
        })
    </script>

reject

Promise.resolve 類似,reject 生成一個(gè)失敗的promise

Promise.reject("fail").catch(error => console.log(error));

下面使用 Project.reject 設(shè)置狀態(tài)

new Promise(resolve => {
  resolve("ac=bc");
})
.then(v => {
  if (v != "123") return Promise.reject(new Error("fail"));
})
.catch(error => {
  console.log(error);
});

all

使用Promise.all 方法可以同時(shí)執(zhí)行多個(gè)并行異步操作闸衫,比如頁(yè)面加載時(shí)同進(jìn)獲取課程列表與推薦課程蔚出。

  • 任何一個(gè) Promise 執(zhí)行失敗就會(huì)調(diào)用 catch方法
  • 適用于一次發(fā)送多個(gè)異步操作
  • 參數(shù)必須是可迭代類型骄酗,如Array/Set
  • 成功后返回 promise 結(jié)果的有序數(shù)組

下例中當(dāng) p1趋翻、p2 兩個(gè) Promise 狀態(tài)都為 fulfilled時(shí)踏烙,ps狀態(tài)才為fulfilled宙帝。

let p1 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve("第一個(gè)Promise");
  }, 1000);
});
let p2 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve("第二個(gè)Promise");
  }, 1000);
});
let ps = Promise.all([p1, p2])
  .then(results => {
    console.log(results);
  })
  .catch(msg => {
    console.log(msg);
  });

根據(jù)用戶名獲取用戶愿待,有任何一人用戶獲取不到時(shí) promise.all 狀態(tài)失敗仍侥,執(zhí)行 catch 方法

<script type="text/javascript">
        function request(url){
            return new Promise((resolve,reject)=>{
                if(!/^http/.test(url)){
                    throw new ParamError('請(qǐng)求地址錯(cuò)誤');
                }
                let XHR = new XMLHttpRequest();
                XHR.onreadystatechange = function(){
                    if(XHR.readyState === 4){
                        if(XHR.status >=200 && XHR.status < 300 || XHR.status === 304){
                            try{
                                let response = JSON.parse(XHR.responseText);
                                resolve(response);
                            }catch(e){
                                reject(e);
                            }
                        }else if(XHR.status === 404){
                            reject(new HttpError('網(wǎng)址不存在'));
                        }else{
                            reject(new Error("Request was unsuccessful: " + XHR.statusText));
                        }
                    }
                }
                XHR.open('GET',url,true);
                XHR.send(null);
            })
        }
        const api = 'http://localhost:3000';
        const promises = ['zhangsan','lisi'].map(name=>{
            return request(`${api}/Course/GetCourseInfoById?name=${name}`)
        })
        Promise.all(promises)
        .then(response=>{
            console.log(response);
        })
        .catch(error=>{
            console.log('Error:' + error);
        })
    </script>

圖片4

可以將其他非promise 數(shù)據(jù)添加到 all 中,它將被處理成 Promise.resolve

const promises = [
  ajax(`${api}/user?name=zhangsan`),
  ajax(`${api}/user?name=lisi`),
  { id: 3, name: "wangwu", email: "wangwu@qq.com" }
];

如果某一個(gè)promise沒有catch 處理砸紊,將使用promise.all 的catch處理

let p1 = new Promise((resolve, reject) => {
  resolve("fulfilled");
});
let p2 = new Promise((resolve, reject) => {
  reject("rejected");
});
Promise.all([p1, p2]).catch(reason => {
  console.log(reason);
});

allSettled

allSettled 用于處理多個(gè)promise 醉顽,只關(guān)注執(zhí)行完成游添,不關(guān)注是否全部執(zhí)行成功唆涝,allSettled 狀態(tài)只會(huì)是fulfilled廊酣。

下面的p2 返回狀態(tài)為 rejected 啰扛,但promise.allSettled 不關(guān)心隐解,它始終將狀態(tài)設(shè)置為 fulfilled

let p1 = new Promise((resolve, reject) => {
  resolve("resolved");
});
let p2 = new Promise((resolve, reject) => {
  reject("rejected");
});
Promise.allSettled([p1, p2])
.then(msg => {
  console.log(msg);
})

下面是獲取用戶信息帕涌,但不關(guān)注某個(gè)用戶是否獲取不成功

let promises = [
  ajax(`${api}/User/GetCourseInfoById?name=zhangsan`),
  ajax(`${api}/User/GetCourseInfoById?name=lisi`)
];
Promise.allSettled(promises).then(response => {
  console.log(response);
});

race

使用Promise.race() 處理容錯(cuò)異步蚓曼,和race單詞一樣哪個(gè)Promise快用哪個(gè)钦扭,哪個(gè)先返回用哪個(gè)客情。

  • 以最快返回的promise為準(zhǔn)
  • 如果最快返加的狀態(tài)為rejected 那整個(gè)promiserejected執(zhí)行catch
  • 如果參數(shù)不是promise膀斋,內(nèi)部將自動(dòng)轉(zhuǎn)為promise

下面將第一次請(qǐng)求的異步時(shí)間調(diào)整為兩秒仰担,這時(shí)第二個(gè)先返回就用第二人摔蓝。

let p1 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve("第一個(gè)Promise");
  }, 2000);
});
let p2 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve("第二個(gè)Promise");
  }, 1000);
});
Promise.race([p1, p2])
.then(results => {
  console.log(results);
})
.catch(msg => {
  console.log(msg);
});

獲取用戶信息哑梳,如果兩秒內(nèi)沒有結(jié)果 promise.race 狀態(tài)失敗,執(zhí)行catch 方法

let promises = [
  ajax(`${api}/User/GetCourseInfoById?name=zhangsan`),
  new Promise((a, b) =>
    setTimeout(() => b(new Error("request fail")), 2000)
  )
];
Promise.race(promises)
.then(response => {
  console.log(response);
})
.catch(error => {
  console.log(error);
});

任務(wù)隊(duì)列

如果 then 返回promise 時(shí)龄毡,后面的then 就是對(duì)返回的 promise 的處理

下面使用 forEach 構(gòu)建的隊(duì)列沦零,有以下幾點(diǎn)需要說明

  • then 內(nèi)部返回的 promise 更改外部的 promise 變量
  • 為了讓任務(wù)繼承路操,執(zhí)行完任務(wù)需要將 promise 狀態(tài)修改為 fulfilled
function queue(nums) {
  let promise = Promise.resolve();
  nums.map(n => {
    promise = promise.then(v => {
      return new Promise(resolve => {
        console.log(n);
        resolve();
      });
    });
  });
}

queue([1, 2, 3, 4, 5]);

下面再來(lái)通過 reduce 來(lái)實(shí)現(xiàn)隊(duì)列

function queue(nums) {
  return nums.reduce((promise, n) => {
    return promise.then(() => {
      return new Promise(resolve => {
        console.log(n);
        resolve();
      });
    });
  }, Promise.resolve());
}
queue([1, 2, 3, 4, 5]);

async/await 語(yǔ)法糖

使用 async/await 是promise 的語(yǔ)法糖屯仗,可以讓編寫 promise 更清晰易懂魁袜,也是推薦編寫promise 的方式峰弹。

  • async/await 本質(zhì)還是promise鞠呈,只是更簡(jiǎn)潔的語(yǔ)法糖書寫
  • async/await 使用更清晰的promise來(lái)替換 promise.then/catch 的方式

async

下面在 foo 函數(shù)前加上async旱爆,函數(shù)將返回promise疼鸟,我們就可以像使用標(biāo)準(zhǔn)Promise一樣使用了空镜。

下面在 foo 函數(shù)前加上async吴攒,函數(shù)將返回promise洼怔,我們就可以像使用標(biāo)準(zhǔn)Promise一樣使用了镣隶。

async function foo() {
  return "foo foo foo";
}
console.log(hd());
foo().then(value => {
  console.log(value);
});

如果有多個(gè)await 需要排隊(duì)執(zhí)行完成安岂,我們可以很方便的處理多個(gè)異步隊(duì)列

async function foo(message) {
  return new Promise(resolve => {
    setTimeout(() => {
      resolve(message);
    }, 2000);
  });
}
async function run() {
  let h1 = await foo("第一次輸出");
  console.log(h1);
  let h2 = await foo("第二次輸出");
  console.log(h2);
}
run();

await

使用 await 關(guān)鍵詞后會(huì)等待promise 完成

  • await 后面一般是promise域那,如果不是直接返回
  • await 必須放在 async 定義的函數(shù)中使用
  • await 用于替代 then 使編碼更優(yōu)雅

下例會(huì)在 await 這行暫停執(zhí)行败许,直到等待 promise 返回結(jié)果后才繼執(zhí)行市殷。

async function foo(message){
    let p =  new Promise(resolve=>{
        setTimeout(()=>{
            resolve(message)
        },2000)
    })
    let result = await p;
    console.log(result);
}
foo('輸出輸出');

一般await后面是外部其它的promise對(duì)象

async function foo(message) {
  return new Promise(resolve => {
    setTimeout(() => {
      resolve(message);
    }, 2000);
  });
}
async function run() {
  let h1 = await foo("第一次輸出");
  console.log(h1);
  let h2 = await foo("第二次輸出");
  console.log(h2);
}
run();

? 錯(cuò)誤處理

async 內(nèi)部發(fā)生的錯(cuò)誤被丧,會(huì)將必變promise對(duì)象為rejected 狀態(tài)甥桂,所以可以使用catch 來(lái)處理

async function foo(){
    console.log(bar);
}
foo().catch(error=>{
    console.log(error);
})

多個(gè) await 時(shí)當(dāng)前面的出現(xiàn)失敗黄选,后面的將不可以執(zhí)行

async function foo() {
  await Promise.reject("fail");
  await Promise.resolve("success").then(value => {
    console.log(value);
  });
}
foo();

如果對(duì)前一個(gè)錯(cuò)誤進(jìn)行了處理,后面的 await 可以繼續(xù)執(zhí)行

async function foo() {
  await Promise.reject("fail").catch(e => console.log(e));
  await Promise.resolve("success").then(value => {
    console.log(value);
  });
}
foo();

也可以使用 try...catch 特性忽略不必要的錯(cuò)誤

async function foo() {
  try {
    await Promise.reject("fail");
  } catch (error) {}
  await Promise.resolve("success").then(value => {
    console.log(value);
  });
}
foo();

? 并發(fā)執(zhí)行
有時(shí)需要多個(gè)await 同時(shí)執(zhí)行民镜,有以下幾種方法處理制圈,下面多個(gè)await 將產(chǎn)生等待

async function p1() {
  return new Promise(resolve => {
    setTimeout(() => {
      console.log("houdunren");
      resolve();
    }, 2000);
  });
}
async function p2() {
  return new Promise(resolve => {
    setTimeout(() => {
      console.log("hdcms");
      resolve();
    }, 2000);
  });
}
async function foo() {
  await p1();
  await p2();
}
foo();

使用 Promise.all() 處理多個(gè)promise并行執(zhí)行

async function foo() {
  await Promise.all([p1(), p2()]);
}
foo();
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市馋嗜,隨后出現(xiàn)的幾起案子葛菇,更是在濱河造成了極大的恐慌熟呛,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,816評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件九府,死亡現(xiàn)場(chǎng)離奇詭異侄旬,居然都是意外死亡儡羔,警方通過查閱死者的電腦和手機(jī)汰蜘,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,729評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)色难,“玉大人枷莉,你說我怎么就攤上這事笤妙∥:恚” “怎么了辜限?”我有些...
    開封第一講書人閱讀 158,300評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)毫深。 經(jīng)常有香客問我哑蔫,道長(zhǎng)闸迷,這世上最難降的妖魔是什么逮走? 我笑而不...
    開封第一講書人閱讀 56,780評(píng)論 1 285
  • 正文 為了忘掉前任师溅,我火速辦了婚禮墓臭,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘榆综。我一直安慰自己鼻疮,他們只是感情好判沟,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,890評(píng)論 6 385
  • 文/花漫 我一把揭開白布挪哄。 她就那樣靜靜地躺著,像睡著了一般斯入。 火紅的嫁衣襯著肌膚如雪刻两。 梳的紋絲不亂的頭發(fā)上磅摹,一...
    開封第一講書人閱讀 50,084評(píng)論 1 291
  • 那天滋迈,我揣著相機(jī)與錄音,去河邊找鬼偏瓤。 笑死杀怠,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的厅克。 我是一名探鬼主播,決...
    沈念sama閱讀 39,151評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼证舟,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了窗骑?” 一聲冷哼從身側(cè)響起女责,我...
    開封第一講書人閱讀 37,912評(píng)論 0 268
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎创译,沒想到半個(gè)月后抵知,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,355評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡软族,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,666評(píng)論 2 327
  • 正文 我和宋清朗相戀三年刷喜,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片立砸。...
    茶點(diǎn)故事閱讀 38,809評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡掖疮,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出颗祝,到底是詐尸還是另有隱情浊闪,我是刑警寧澤,帶...
    沈念sama閱讀 34,504評(píng)論 4 334
  • 正文 年R本政府宣布螺戳,位于F島的核電站搁宾,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏倔幼。R本人自食惡果不足惜盖腿,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,150評(píng)論 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望凤藏。 院中可真熱鬧奸忽,春花似錦、人聲如沸揖庄。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,882評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)蹄梢。三九已至疙筹,卻和暖如春富俄,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背而咆。 一陣腳步聲響...
    開封第一講書人閱讀 32,121評(píng)論 1 267
  • 我被黑心中介騙來(lái)泰國(guó)打工霍比, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人暴备。 一個(gè)月前我還...
    沈念sama閱讀 46,628評(píng)論 2 362
  • 正文 我出身青樓悠瞬,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親涯捻。 傳聞我的和親對(duì)象是個(gè)殘疾皇子浅妆,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,724評(píng)論 2 351

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