Promise

Promise由淺入深

前言

我們都知道javaScript是單線程的,代碼執(zhí)行的順序是從上往下。但是在實際開發(fā)中難免會出現(xiàn)異步編程。所謂異步就是代碼執(zhí)行到這里不會立即執(zhí)行得出結(jié)果,比如ajax請求,文件的讀取等,為了不阻塞進程,先把它放到事件隊列中。

但是傳統(tǒng)的ajax的請求方式有一個很大問題,比如有多個請求并且下次的請求需要依賴上次的請求結(jié)果,那么就會出現(xiàn)多層嵌套,也就是所謂的“回調(diào)地獄”。

es6中提出了Promise。可以作為解決異步的一種方式。promise可以理解成是一個對象的代理,通常用來代理一個未來的值,為什么說是未來呢,因為這個值通常是由異步操作得出的,不像同步操作那樣立即就可以拿到值。
我們可以把Promise比做一個容器,它里邊包含著未來才會結(jié)束的事件的結(jié)果。

Promise有三種狀態(tài):

  • pending 初始狀態(tài)
  • fulfilled 成功完成
  • rejected 失敗

1、 聲明未執(zhí)行的Promise實例是pending狀態(tài),執(zhí)行成功調(diào)用resolve方法是fulfilled狀態(tài),執(zhí)行失敗調(diào)用rejecte方法是rejected狀態(tài)。

2、 Promise狀態(tài)一旦改變,就不會在變了。也就是說狀態(tài)的改變只有兩種可能:(1)從pending變成fulfilled (2)從pending變成rejected。只要狀態(tài)一旦改變,狀態(tài)就凝固了,不會在發(fā)生改變。

Promise參數(shù)

Promise接受一個函數(shù)參數(shù),函數(shù)參數(shù)又有兩個參數(shù),分別是resolve和rejecte。在函數(shù)參數(shù)中執(zhí)行相應(yīng)的異步操作,當執(zhí)行成功,調(diào)用resolve,執(zhí)行失敗調(diào)用rejecte。通過reslove和rejecte傳遞結(jié)果。

Promise的前因后果

new Promise

var promise = new Promise(function (resolve, reject) {
    console.log('create promise')
    setTimeout(function () {
        resolve('first value');
        console.log('最后執(zhí)行');
    }, 1000);
        
})

// 實例創(chuàng)建完成,函數(shù)內(nèi)部有異步操作,不會立即執(zhí)行resolve方法,所以狀態(tài)為pending
console.log(promise)

輸出先后順序:

"create promise"
Promise {[[PromiseStatus]]: "pending", [[PromiseValue]]: undefined}
"最后執(zhí)行"

從輸出的先后順序可以看出來Promise的創(chuàng)建以及函數(shù)參數(shù)的執(zhí)行是一個同步的過程,實例創(chuàng)建完成后由于函數(shù)參數(shù)中有一個異步操作,不會立即執(zhí)行resolve方法,所以Promise的實例仍是pending狀態(tài),。

Promise實例構(gòu)造函數(shù)的原型上有then方法和catch方法,可以被其實例所共享。其中then方法接受兩個函數(shù)參數(shù),第一個函數(shù)的參數(shù)就是resolve中接收的值,第二個函數(shù)是錯誤處理;catch方法是rejecte中的值。

Promise實例調(diào)用then方法的實現(xiàn)思路:

// 偽代碼
Promise.prototype.then = function(success, faile){
    // then方法的偽代碼 執(zhí)行相應(yīng)的函數(shù)參數(shù)
    // 如果異步操作成功執(zhí)行success方法
    success();
    // 反之
    faile();
    // 最后返回一個新的Promise對象
    return newPromise;
}

鏈式調(diào)用

因為then方法的兩個函數(shù)參數(shù)默認會返回一個新的Promise對象,所以可以進行鏈式調(diào)用。調(diào)用規(guī)則:

  • 當沒有顯式return時,默認返回新的Promise對象;
  • 當return的不是Promise對象時,那么return的值會作為then返回的新Promise實例的resolved參數(shù);
  • 當return的是一個Promise實例時,那么then返回的Promise實例就是該實例;

具體如下:

promise.then(function (response) {
    console.log(response); // first value
    console.log(promise); // Promise對象 狀態(tài)已經(jīng)由pending 變?yōu)?resolved
}).then(function (response) {
    console.log(response); // undefined 
    // 從這里可以看出來,then()方法返回的是一個新的promise,因為新的Promise實例中并沒有執(zhí)行resolve()方法,更沒有什么異步操作,所以這里的值是undefined(聲明未初始化)

    // 手動創(chuàng)建并返回一個新的Promise實例 
    return new Promise(function (resolve, reject) {
        setTimeout(function () {
            resolve('second value')
        }, 1000);
    })
}).then(function(response){
    console.log(response); // second value
    // 這里之所以能夠獲取到值,是因為上一個then()返回的Promise成功執(zhí)行異步操作

    return '還可以返回非Promise實例'
}).then(function(response){
    console.log(response) // 還可以返回非Promise實例
})

下面通過簡單的示例一步步深入了解。

一、Promise立即執(zhí)行

var promise = new Promise(function(resolved, rejected){
    console.log('創(chuàng)建promise')
    resolved('成功');
    rejected('失敗');
})

console.log('執(zhí)行完成')

promise.then(function(res){
    console.log(res);
}).catch(function(err){
    console.log(err);
})

輸出結(jié)果:

創(chuàng)建Promise
執(zhí)行完成
成功

解釋:Promise對象表示將來某一時刻要發(fā)生的事件,但是創(chuàng)建(new)Promise是一個同步的過程,包括傳入的函數(shù)參數(shù)也是一個立即執(zhí)行的函數(shù),只是函數(shù)內(nèi)部是異步操作還是同步操作就不確定了。所以以上代碼是按順序輸出。

二、Promise的三種狀態(tài)

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

p1、p2、p3的狀態(tài)
console.log(p1);  // resolved
console.log(p2);  // pending
console.log(p3);  // pending

setTimeout(function(){
  console.log(p2); // resolved
}, 1000);
setTimeout(function(){
  console.log(p3); // rejected
}, 1000);


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

輸出結(jié)果:

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內(nèi)部理解成是一個狀態(tài)機,分別有pending resolved rejected三種狀態(tài)。 當剛創(chuàng)建Promise時處于pending狀態(tài),所以p2,p3的狀態(tài)為pending,因為p1的函數(shù)參數(shù)執(zhí)行的是同步代碼,Promise剛創(chuàng)建完成resolved也就執(zhí)行了,所以p1是resolved狀態(tài)。然后又是兩個setTimeout函數(shù),所以會先分別執(zhí)行對應(yīng)的then方法,輸出 1、 2、 3;1s后執(zhí)行setTimeout函數(shù),此時p2 p3的狀態(tài)分別已經(jīng)變成了resolved和rejected。

三、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);
});

輸出結(jié)果:

'success1'
'success'

解釋:Promise狀態(tài)一旦從pending變成resolved或rejected時狀態(tài)就凝固了。不管再調(diào)用resolved或rejected都不管用。

四、鏈式調(diào)用

    var p = new Promise(function (resolve, reject) {
        resolve(1);
    });
    
    p.then(function (value) {         
        console.log(value);  // 1
        return value * 2;
    }).then(function (value) {     
        console.log(value);  // 2
    }).then(function (value) {       
        console.log(value);  // undefined
        return Promise.resolve('resolve');
    }).then(function (value) {               
        console.log(value);  // resolve
        return Promise.reject('reject');
    }).then(function (value) {               
        console.log('resolve: ' + value);
    }).catch(function(err){
        console.log("rejected: ", err) // reject
    }) 

輸出結(jié)果:

1
2
"undefined"
"resolved"
"rejected: rejected"

解釋: Promise對象的then方法返回一個新的Promise對象,因此可以鏈式調(diào)用。第一個then有返回值,所以在二個then中輸出2;而第二個then沒有返回值,所以第三個then輸出undefined;第三個then返回一個Promise的resolved方法,所以第四個then中輸出resolved;但是第四個then中返回Promise的rejected方法,所以第五個then的第一個函數(shù)參數(shù)不會輸出,而是在第二個函數(shù)參數(shù)或者catch中輸出rejected。

五、Promise then()回調(diào)的異步性

    var promise = new Promise(function(resolved, rejected){
        resolved('success')
        console.log('first')
    })

    promise.then(function(res){
        console.log(res)
    })

    console.log('second')

輸出結(jié)果:

'first'
'second'
'success'

解釋:new Promise、函數(shù)參數(shù)以及其內(nèi)部的代碼是一個同步的過程,所以會先輸出 first,但是then方法中的回調(diào)函數(shù)是異步的,所以先輸出 second,最后輸出 success。

六、Promise中的異常

var p1 = new Promise( function(resolve,reject){
  foo.bar();
  resolve( 1 );      
});
console.log(p1) 
// Promise {[[PromiseStatus]]: "rejected", [[PromiseValue]]: ReferenceError: foo is not defined}

p1.then(
  function(value){
    console.log('p1 then1 value: ' + value);
  },
  function(err){
    console.log('p1 then1 err: ' + err);// 報錯 foo is not defined
    // 沒有顯示返回
  }
).catch(function(err){
    console.log('err', err)
}).then(
  function(value){
    console.log('p1 then2  value: '+value); // p1 then2 value: undefined 
    
  },
  function(err){
    console.log('p1 then2  err: ' + err);
  }
);

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

p2.then(
  function(value){
    console.log('p2 then1 value: ' + value);// p2 then1 value: 2
    foo.bar(); // 報錯
  }, 
  function(err){
    console.log('p2 then1 err: ' + err);
  }
).then(
  function(value){
    console.log('p2 then2 value: ' + value);
  },
  function(err){
    console.log('p2 then2 err: ' + err); // p2 then2 err: foo is not defined
    return 1;
  }
).then(
  function(value){
    console.log('p2 then3 value: ' + value);// p2 then3 value: 1
  },
  function(err){
    console.log('p2 then then then err: ' + err);
  }
);

輸出結(jié)果:

p1 then1 err: ReferenceError: foo is not defined
p2 then1 value: 2
p2 then2 err: ReferenceError: foo is not defined
p1 then2  value: undefined
p2 then3 value: 1

解釋:Promise中的異常由then參數(shù)中第二個回調(diào)函數(shù)(Promise執(zhí)行失敗的回調(diào))處理,異常信息將作為Promise的值。異常一旦得到處理,then返回的后續(xù)Promise對象將恢復(fù)正常,并會被Promise執(zhí)行成功的回調(diào)函數(shù)處理。另外,p1、p2 多個then的回調(diào)是交替執(zhí)行的 ,這正是由Promise then回調(diào)的異步性決定的。

七、Promise.resolve()

Promise.resolve(value)返回給定給定值解析后的Promise對象。

value分三種情況:

  1. 普通值 --- 以該值作為resolved狀態(tài)返回promise
Promise.resolve("Success").then(function(value) {
  console.log(value); // "Success"
}, function(value) {
  // 不會被調(diào)用
});
  1. Promise對象 --- 返回值即為該傳入的Promise對象
let p1 = Promise.resolve('success');
let p2 = Promise.resolve(p1);
cast.then(value => {
  console.log('value: ' + value);
});
console.log('p1 === p2 ? ' + (p1 === p2));

/*
*  打印順序如下,這里有一個同步異步先后執(zhí)行的區(qū)別
*  p1 === p2 ? true
*  value: success
*/
  1. 帶有then方法的對象 --- 返回的promise會“跟隨”這個thenable的對象,采用它的最終狀態(tài)(指resolved/rejected/pending/settled)
// Resolve一個thenable對象
var p1 = Promise.resolve({ 
    then(onFulfill, onReject) { 
        onFulfill("fulfilled!");
    }
});
console.log(p1 instanceof Promise) // true, 這是一個Promise對象

p1
    .then(res => {
        console.log(res); // "fulfilled!"
    })
    .catch(err => {
        console.log(err)
    })

// Thenable在callback之前拋出異常
let thenable = { 
    then(resolve) {
        throw new Error("Throwing");
        resolve("Resolving")
    }
};

let p2 = Promise.resolve(thenable);

p2
    .then(res => {
        console.log(res)
    })
    .catch(err => {
        console.log(err)  // Throwing
    })

// Thenable在callback之后拋出異常
let thenable = { 
    then(resolve) {
        resolve("Resolving");
        throw new Error("Throwing");
    }
}

let p3 = Promise.resolve(thenable);
p3  
    .then(res => {
        console.log(res) // Resolving
    })
    .catch(err => {
        console.log(err)
    })

    let thenable = { 
    then(resolve) {
        resolve("Resolving");
        throw new Error("Throwing");
    }
}


let thenable = { 
    then(resolve, reject) {
        reject("Oops")
    }
}

let p4 = Promise.resolve(thenable);
p4  
    .then(res => {
        console.log(res) 
    })
    .catch(err => {
        console.log(err) // oops
    })
  1. resolve異步解析Promise對象
let p1 = Promise.resolve( 1 );
let p2 = Promise.resolve( p1 );
let p3 = new Promise(function(resolve, reject){
  resolve(1);
});
let p4 = new Promise(function(resolve, reject){
  resolve(p1);
});

console.log(p1 === p2); // true
console.log(p1 === p3); // false
console.log(p1 === p4); // false
console.log(p3 === p4); // false

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

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

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

輸出結(jié)果:

true
false
false
false

p2=1
p1=1
p4=1

解釋:Promise.resolve()中的參數(shù)可以是一個普通值或者一個Promise對象,如果是普通值,則它返回一個Promise對象,PromiseValue就是該值;如果參數(shù)是Promise對象,那么直接返回這個Promise對象參數(shù)。所以p1==p2。但是因為通過new的方式創(chuàng)建的Promise對象都是一個新的對象,所以其余都是false。

為什么p4的then最先調(diào)用,但在控制臺上是最后輸出結(jié)果的呢?因為p4的resolve中接收的參數(shù)是一個Promise對象p1,resolve會對p1”提取“,獲取p1的狀態(tài)和值,但這個過程是異步的。也就是說Promise中的resolve方法會對接收的Promise對象進行“分解”。

八、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'));// 拆箱獲取值 reject
});

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



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

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

p3.then(
  function (value){
      console.log('p3', value)
    console.log('p3 fulfilled: ' + value);
  }, 
  function (err){
    console.log(err) // Promise {[[PromiseStatus]]: "resolved", [[PromiseValue]]: "resolve"}
    console.log('p3 rejected: ' + err); // rejected [object Promise]
  }
);


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

輸出結(jié)果:

Promise {[[PromiseStatus]]: "resolved", [[PromiseValue]]: "resolve"}
p3 rejected: [object Promise]
p1 resolve
p1 fulfilled: resolve
p2 err reject
p2 rejected: reject

Promise {[[PromiseStatus]]: "resolved", [[PromiseValue]]: "resolve"}
{[[PromiseStatus]]: "rejected", [[PromiseValue]]: "reject"}
{[[PromiseStatus]]: "rejected", [[PromiseValue]]: Promise}

解釋:
Promise回調(diào)中的第一個參數(shù)resolve,會對Promise執(zhí)行"提取"。即當resolve的參數(shù)是一個Promise對象時,resolve會"提取"獲取這個Promise對象的狀態(tài)和值,(原封不動的返回傳入的promise對象)但這個過程是異步的。

p1"提取"后,獲取到Promise對象的狀態(tài)是resolved,因此p1.then()中的第一個回調(diào)被執(zhí)行;p2"提取"后,獲取到Promise對象的狀態(tài)是rejected,因此p2.then()中的第二個回調(diào)被執(zhí)行。

但Promise回調(diào)函數(shù)中的第二個參數(shù)reject不具備”提取“的能力,reject的參數(shù)會直接傳遞給then方法中的第二個回調(diào)。因此,即使p3 reject接收了一個resolved狀態(tài)的Promise,then方法中被調(diào)用的依然是rejected,并且參數(shù)就是reject接收到的Promise對象。

參考: MDN
阮一峰 Promise

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 00、前言Promise 是異步編程的一種解決方案,比傳統(tǒng)的解決方案——回調(diào)函數(shù)和事件——更合理和更強大。它由社區(qū)...
    夜幕小草閱讀 2,226評論 0 12
  • Promise的含義: ??Promise是異步編程的一種解決方案,比傳統(tǒng)的解決方案——回調(diào)函數(shù)和事件——更合理和...
    呼呼哥閱讀 2,270評論 0 16
  • 1、含義所謂Promise,簡單說就是一個容器,里面保存著某個未來才會結(jié)束的事件(通常是一個異步操作)的結(jié)果。從語...
    秋天de童話閱讀 834評論 0 1
  • 參數(shù)組合 在Python中定義函數(shù),可以用必選參數(shù)、默認參數(shù)、可變參數(shù)、關(guān)鍵字參數(shù)和命名關(guān)鍵字參數(shù),這5種參數(shù)都可...
    ragna閱讀 410評論 0 0
  • 隨拍下來方便練習
    奧龍流星閱讀 389評論 0 1

友情鏈接更多精彩內(nèi)容