天天看點

JavaScript-Promise基礎知識

Promise是ES6中新增的一個類,專門用來解決異步回調地獄的問題,異步代碼同步顯示出來。

回調函數的例子

//成功回調函數
function successCallback() { // 固定格式
    console.log('SUCCESS')
}
//失敗回調函數
function failureCallback() { // 固定格式
    console.log('FAILURE')
}

function myFunc(param, success, failure) {
    if (param) {
        success()
    } else {
        failure()
    }
}

myFunc(0, successCallback, failureCallback) // FAILURE
myFunc(1, successCallback, failureCallback) // SUCCESS      

以上回調函數可用promise實作

var t = 0;
var promise = new Promise(function(resolve, reject) {
    if (t%2 == 0) {
        resolve();
    } else {
        reject();
    }
});
promise.then(function() {
    console.log('SUCCESS');
}, function(){
    console.log('FAILURE');
});

運作結果:

SUCCESS      

注:new了一個Promise對象,沒調用它,函數就已經執行了。是以使用Promise時一般包在一個函數中,在需要時調用。

是以,上面的執行個體可以包在函數fn()裡面,需要時就調用fn()

function fn() {
    var t = 1;
    var promise = new Promise(function(resolve, reject) {
        if (t%2 == 0) {
            console.log(1);
            resolve();
            console.log(1.1);
        } else {
            console.log(2);
            reject();
            console.log(2.1);
        }
    });
    promise.then(function() {
        console.log(3);
        console.log('SUCCESS');
        console.log(3.1);
    }, function(){
        console.log(4);
        console.log('FAILURE');
        console.log(4.1);
    });

    return promise;
}
fn();      
運作結果:

2
2.1
4
FAILURE
4.1      

Promise運作流程

JavaScript-Promise基礎知識
JavaScript-Promise基礎知識
function test_01() {
    new Promise(function(resolve, reject) {
        setTimeout(() => {
            resolve('success....'),
            reject('fail...')
        }, 1000);
    }).then(
        value => {
            console.log('onResolved()1', value);
        }
    ).catch(
        reason => {
            console.log('onRejected()1', reason);
        }
    )
}
test_01();      

輸出結果:onResolved()1 success....

setTimeout中resolve/reject隻能執行一個,要麼成功、要麼失敗。

function test_02() {
    // 産生一個成功值為1的promise對象
    const p1 = new Promise(function(resolve, reject) {
        resolve(1)
    })
    // 産生一個成功值為2的promise對象
    const p2 = Promise.resolve(2);
    // 産生一個失敗值為3的promise對象
    const p3 = Promise.reject(3);

    p1.then(value => {console.log(value)})
    p2.then(value => {console.log(value)})
    // p3.then(null, reason => {console.log(reason)}) // 寫法1
    // p3.then(null, value => {console.log(value)}) // 寫法2
    // p3.catch(value => {console.log(value)}) // 寫法3
    p3.catch(reason => {console.log(reason)}) // 寫法4
}
test_02();      

輸出結果:1 2 3

function test_01() {
    new Promise(resolve => {
        console.log(123);
    }).then(value => {console.log(456)})
    console.log("hi")
}
test_01();      

執行結果:123 hi

注:then沒有被執行,是因為沒有resolve通知。隻有得到通知才會進入微任務隊列。

Promise異常捕獲

Promise.prototype.catch方法是Promise.prototype.then(null, rejection)的别名,用于指定發生錯誤時的回調函數。

Promise對象的錯誤具有“冒泡”性質,會一直向後傳遞,直到被捕獲為止。也就是說,錯誤總是會被下一個catch語句捕獲。

getJSON("/visa.json").then(function(json) {
return json.name;
}).then(function(name) {
// proceed
}).catch(function(error) {
//處理前面任一個then函數抛出的錯誤
});      
<script>
function test_01() {
    const p = new Promise(function(resolve, reject) {
        setTimeout(() => {
            // resolve('success....'), // promise變為resolved 成功狀态
            // reject('fail...') // promise變為rejected 失敗狀态
            throw new Error('error....') // 抛出異常,promise變為pending狀态,value為'error...'
        }, 1000);
    });
    console.log(p);
}
test_01();
</script>      
JavaScript-Promise基礎知識

先改狀态,還是先改值?

function test_03() { // 正常:先執行回調函數,後改狀态
    new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve(1); // 2. 改變狀态(pending → resolved, 同時調用回調函數指定資料)setTimeout為宏任務
        },1000)
    }).then( // 1. 執行回調函數(微任務),儲存目前指定的回調函數
        value => {},
        reason => {}
    )
}      
function test_04() {// 先改狀态,後執行回調函數
    new Promise((resolve, reject) => {
        resolve(1); // 1. 改變狀态(pending → resolved, 同時調用回調函數指定資料)
    }).then( // 2. 後執行回調函數(微任務),異步執行回調函數
        value => {},
        reason => {}
    )
}      

Promise狀态改變是不可逆的,pending → resolved之後,無法再轉為其他狀态。

是以,resolve / reject 隻能執行一個。

Promise鍊式調用

  • 回調地獄
  • 代碼難閱讀
  • 多個串聯函數适合使用回調函數嵌套

鍊式Promise是指在目前promise達到fulfilled狀态後,即開始進行下一個promise(後鄰promise)。如何銜接目前promise和後鄰promise呢?(這是這裡的難點)。隻要在then方法裡面return一個promise就好了(得到一個promise是前提)。

注意: 一定要有傳回值,否則,callback 将無法擷取上一個 Promise 的結果。

(如果使用箭頭函數,() => x 比 () => { return x; } 更簡潔一些,但後一種保留 return 的寫法才支援使用多個語句。)。

function runAsync1(){
    var p = new Promise(function(resolve, reject){
        //做一些異步操作
        setTimeout(function(){
            console.log('異步任務1執行完成');
            resolve('随便什麼資料1');
        }, 1000);
    });
    return p;            
}
function runAsync2(){
    var p = new Promise(function(resolve, reject){
        //做一些異步操作
        setTimeout(function(){
            console.log('異步任務2執行完成');
            resolve('随便什麼資料2');
        }, 2000);
    });
    return p;            
}
function runAsync3(){
    var p = new Promise(function(resolve, reject){
        //做一些異步操作
        setTimeout(function(){
            console.log('異步任務3執行完成');
            resolve('随便什麼資料3');
        }, 2000);
    });
    return p;            
}

runAsync1()
.then(function(data){
    console.log(data);
    return runAsync2();
})
.then(function(data){
    console.log(data);
    return runAsync3();
})
.then(function(data){
    console.log(data);
});

運作結果:

異步任務1執行完成
随便什麼資料1
異步任務2執行完成
随便什麼資料2
異步任務3執行完成
随便什麼資料3      

如果then不傳回Promise對象,則後面then不再受到資料。

function runAsync1(){
    var p = new Promise(function(resolve, reject){
        //做一些異步操作
        setTimeout(function(){
            console.log('異步任務1執行完成');
            resolve('随便什麼資料1');
        }, 1000);
    });
    return p;            
}
function runAsync2(){
    var p = new Promise(function(resolve, reject){
        //做一些異步操作
        setTimeout(function(){
            console.log('異步任務2執行完成');
            resolve('随便什麼資料2');
        }, 2000);
    });
    return p;            
}
function runAsync3(){
    var p = new Promise(function(resolve, reject){
        //做一些異步操作
        setTimeout(function(){
            console.log('異步任務3執行完成');
            resolve('随便什麼資料3');
        }, 2000);
    });
    return p;            
}

runAsync1()
.then(function(data){
    console.log(data);
    return runAsync2();
})
.then(function(data){
    console.log(data);
    return '直接傳回資料';  //這裡直接傳回資料
})
.then(function(data){
    console.log(data);
});

運作結果:

異步任務1執行完成
随便什麼資料1
異步任務2執行完成
随便什麼資料2
直接傳回資料      

promise裡面的then函數僅僅是注冊了後續需要執行的代碼,真正的執行是在resolve方法裡面執行的,理清了這層,再來分析源碼會省力的多。

連結:

(ES6 Promise 用法)

Promise設計模式 (​​https://zhuanlan.zhihu.com/p/42377418​​ (30分鐘,讓你徹底明白Promise原理))

Promise的實作過程,其主要使用了設計模式中的觀察者模式:

繼續閱讀