什麼是Prmoise
Promise 是非同步程式設計的一種解決方案,比傳統的解決方案—回撥函式和事件—更合理和更強大。它由社群最早提出和實現,ES6 將其寫進了語言標準,統一了用法,原生提供了Promise物件。
Promise物件可以理解為一次執行的非同步操作,使用promise物件之後可以使用一種鏈式呼叫的方式來組織程式碼;讓程式碼更加的直觀。也就是說,有了Promise物件,就可以將非同步操作以同步的操作的流程表達出來,避免了層層巢狀的回撥函式。
Promise物件的特點
1、物件的狀態不受外界影響。
Promise物件代表一個非同步操作,有三種狀態
- pending(執行中)
- Resolved(成功,又稱Fulfilled)
- rejected(拒絕)
其中pending為初始狀態,fulfilled和rejected為結束狀態(結束狀態表示promise的生命週期已結束)。
promise只有非同步操作的結果,可以決定當前是哪一種狀態,任何其他操作都無法改變這個狀態。
2、一旦狀態改變,就不會再變,任何時候都可以得到這個結果。
Promise物件的狀態改變,只有兩種可能:從Pending變為Resolved和從Pending變為Rejected,只要這兩種情況發生,狀態就凝固了,不會再變了,會一直保持這個結果
Promise物件的缺點:
1、無法取消Promise,一旦新建它就會立即執行,無法中途取消。
2、如果不設定回撥函式,Promise內部丟擲的錯誤,不會反應到外部。
3、當處於Pending狀態時,無法得知目前進展到哪一個階段(剛剛開始還是即將完成)
如果某些事件不斷地反覆發生,一般來說,使用 Stream 模式是比部署Promise更好的選擇。
Promise的使用
1、基本用法:
(1)先new一個Promise,將Promise例項化
(2)然後在例項化的promise傳兩個引數,一個是成功之後的resolve,一個是失敗之後的reject
(3)Promise例項生成以後,可以用then
方法分別指定Resolved狀態和Reject狀態的回撥函式。then
方法可以接受兩個回撥函式作為引數。第一個回撥函式是Promise物件的狀態變為resolved時呼叫,第二個回撥函式是Promise物件的狀態變為rejected時呼叫。其中,第二個函式是可選的,不一定要提供。這兩個函式都接受Promise物件傳出的值作為引數。
var promise = function(isReady){
return new Promise(function(resolve, reject){
// do somthing, maybe async
if (isReady){
return resolve('hello world');
} else {
return reject('failure');
}
});
}
//Promise例項生成以後,可以用then方法分別指定Resolved狀態和Reject狀態的回撥函式。
promise(true).then(function(value){
// success,這裡是resolve的回撥函式
console.log(value); //hello world
}, function(err){
// failure,這裡是reject的回撥函式
console.log(err)
})
複製程式碼
2、鏈式呼叫
then()
方法的作用是Promise例項新增解決(fulfillment)和拒絕(rejection)狀態的回撥函式。then()
方法會返回一個新的Promise例項,所以then()
方法後面可以繼續跟另一個then()
方法進行鏈式呼叫。
let p = new Promise((resolve, reject) => {
setTimeout(resolve, 1000, 'success');
});
p.then(
res => {
console.log(res);
return new Promise((resolve, reject) => {
setTimeout(resolve, 1000, 'success');
});
}
).then(
res => console.log(res)
);
// success
// 1000ms後
// success
複製程式碼
3、catch方法 其實它和then的第二個引數一樣,用來指定reject的回撥,不過它還有另外一個作用:在執行resolve的回撥(也就是上面then中的第一個引數)時,如果丟擲異常了(程式碼出錯了),那麼並不會報錯卡死js,而是會進到這個catch方法中。
getNumber()
.then(function(data){
console.log('resolved');
console.log(data);
console.log(someData); //此處的someData未定義
})
.catch(function(reason){
console.log('rejected');
console.log(reason);
});
複製程式碼
如果我們不用Promise,程式碼執行到console就直接在控制檯報錯了,不往下執行了。但是在這裡,進到catch方法裡面去了,而且把錯誤原因傳到了reason引數中。即便是有錯誤的程式碼也不會報錯了,這與我們的try/catch語句有相同的功能。
4、all的用法
Promise.all
方法用於將多個 Promise 例項,包裝成一個新的 Promise 例項。Promise.all
可以接收一個元素為 Promise 物件的陣列作為引數,當這個陣列裡面所有的 Promise 物件都變為 resolve 時,該方法才會返回。(Promise.all
方法的引數可以不是陣列,但必須具有 Iterator 介面,且返回的每個成員都是 Promise 例項。)
var p1 = new Promise(function (resolve) {
setTimeout(function () {
resolve("promise1");
}, 2000);
});
var p2 = new Promise(function (resolve) {
setTimeout(function () {
resolve("promise2");
}, 1000);
});
Promise.all([p1, p2]).then(function (result) {
console.log(result); // ["promise1", "第二個promise2"]
});
複製程式碼
5、race的用法
promise.race
也是傳入一個陣列,但是與promise.all不同的是,race只返回跑的快的值,也就是說result返回比較快執行的那個。
var p1 = new Promise(function (resolve) {
setTimeout(function () {
console.log(1);
resolve("promise1");
}, 2000);
});
var p2 = new Promise(function (resolve) {
setTimeout(function () {
console.log(2);
resolve("promise2");
}, 1000);
});
Promise.race([p1, p2]).then(function (result) {
console.log(result);
});
// 結果:
// 2
// promise2
// 1
複製程式碼
可以看到,傳的值中,只有p2的返回了,但是p1沒有停止,依然有執行。
race的應用場景為,比如我們可以設定為網路請求超時。寫兩個promise,如果在一定的時間內如果成功的那個我們沒有執行到,我們就執行失敗的那個.
手寫Promise
手寫實現Promise程式碼:
function resolvePromise(promise2,x,resolve,reject){
//判斷x是否等於promise
//promiseA+規定了一段程式碼,可以實現promise之間的互動
if(promise2 === x){//不能自己等待自己完成
return reject(new TypeError('迴圈引用'))
}
if(x != null && (typeof x === 'object' || typeof x === 'function')){
//不是null 也不是物件和函式
let called;//防止成功後再呼叫失敗
try{//防止取then時報錯
let then = x.then;
if(typeof then === 'function'){//如果是函式就認為它是promise
//call第一引數是this,後面是成功的和失敗的回撥
then.call(x,y=>{
if(called)return;
called = true;
resolvePromise(promise2,y,resolve,reject)//遞迴
},r=>{
if(called)return;
called = true;
reject(r)
})
}else{//then是個普通物件 直接成功即可
resolve(x)
}
}catch(e){
if(called)return;
called = true;
reject(e)
}
}else{//x就是個普通值
resolve()
}
}
class Promise{
constructor(executor){
this.status = 'pending';//預設等待狀態
this.value = undefined;
this.reason = undefined;
this.onResolvedCb = [];//存放成功的回撥
this.onRejectedCb = [];//存放失敗的回撥
let resolve = (data) => {
if(this.status === 'pending'){
this.value = data;
this.status = 'resolved';
this.onResolvedCb.forEach(fn=>fn());//解決非同步問題
}
}
let reject = (reason) => {
if(this.status === 'pending'){
this.reason = reason;
this.status = 'rejected';
this.onRejectedCb.forEach(fn=>fn());
}
}
try{//執行時可能會發生異常
executor(resolve,reject);
}catch(e){
reject(e)
}
}
then(onFullFilled,onRejected){
//then不傳參的時候
onFullFilled = typeof onFullFilled === 'function' ? onFullFilled : y => y;
onRejected = typeof onRejected === 'function' ? onRejected : err => { throw err };
let promise2;//鏈式呼叫
if(this.status === 'resolved'){
// onFullFilled(this.value);
promise2 = new Promise((resolve,reject)=>{
try{
let x = onFullFilled(this.value);
//看x是不是promise 是promise或者是個普通值的話 作為promise2的成功結果
resolvePromise(promise2,x,resolve,reject)
//resolvePromise可以解析x和promise之間的關係
}catch(e){
reject(e)
}
})
return promise2;//呼叫then後返回一個新的promise
}
if(this.status === 'rejected'){
promise2 = new Promise((resolve,reject)=>{
try{
let x = onFullFilled(this.reason);
resolvePromise(promise2,x,resolve,reject)
}catch(e){
reject(e)
}
})
return promise2;
}
if(this.status === 'pending'){//既沒有成功也沒有失敗(先執行了then)
promise2 = new Promise((resolve,reject)=>{
this.onResolvedCb.push(()=>{//存放成功回撥
try{
//還可以做其他操作
let x = onFullFilled(this.value)
resolvePromise(promise2,x,resolve,reject)
}catch(e){
reject(e)
}
})
this.onRejectedCb.push(()=>{//存放失敗回撥
try{
//還可以做其他操作
let x = onRejected(this.reason)
resolvePromise(promise2,x,resolve,reject)
}catch(e){
reject(e)
}
})
})
return promise2;
}
}
catch(onRejected){
return this.then(null,onRejected);
}
}
Promise.resolve = function(val){
return new Promise((resolve,reject)=>resolve(val))
}
Promise.reject = function(val){
return new Promise((resolve,reject)=>reject(val))
}
Promise.all = function(promises){
return new Promise((resolve,reject)=>{
let arr=[],i=0;
function processData(index,data){
arr[index]=data;
i++;
if(i ===promises.length){
resolve(arr)
}
}
for(let i=0;i<promises.length;i++){
promises[i].then(data=>{
processData(i,data)
},reject)
}
})
}
Promise.race = function(promises){
return new Promise((resolve,reject)=>{
for (let i = 0; i < promises.length; i++) {
promises[i].then(resolve,reject)
}
})
}
//promise 語法糖
Promise.deferred = Promise.defer = function () {
let dfd = {};
dfd.promise = new Promise((resolve,reject)=>{
dfd.resolve = resolve;
dfd.reject = reject;
})
return dfd;
}
// npm install promises-aplus-tests -g
// promises-aplus-test 檔名
module.exports = Promise;
複製程式碼