1. 程式人生 > >ES6中的Promise使用方法與總結

ES6中的Promise使用方法與總結

在javascript中,程式碼是單執行緒執行的,對於一些比較耗時的IO操作,都是通過非同步回撥函式來實現的。

但是這樣會存在一個問題,當下一個的操作需要上一個操作的結果時,我們只能把程式碼嵌到上一個操作的回撥函式裡,這樣一層嵌一層,最終形成回撥地獄。

$.get('/login.php', function (login) {
    $.get('/user.php', function (user) {
        $.get('/info.php', function (info) {
            //程式碼就這樣一層嵌一層,不夠直觀,維護也麻煩
        });
    });
});

為了解決這種問題,ES6中就提供了Promise方法來解決這種問題。

Promise是一個建構函式,通過它,我們可以建立一個Promise例項物件。

let p = new Promise(function (resolve, reject) {
    setTimeout(() => {
        console.log('OK');
        resolve('OK');
    }, 1000);
});

Promise建構函式接受一個函式作為引數,這個函式有兩個引數,resolve和reject。

resolve函式是將Promise的狀態設定為fulfilled(完成),reject函式是將Promise的狀態設定為rejected(失敗)。

上述程式碼,我們並沒有進行任何呼叫,當執行時,間隔1秒後輸出了'OK'。所以這裡需要注意,我們通常使用Promise時,需要在外層再包裹一層函式。

let p = function () {
    return new Promise(function (resolve, reject) {
        setTimeout(() => {
            console.log('OK');
            resolve('OK');
        }, 1000);
    });
};

p();

上面的程式碼p();返回的是一個Promise例項物件,Promise物件上有 then() , catch() , finally() 方法。

then方法有兩個引數,onFulfilled和onRejected,都是函式。

onFulfilled用於接收resolve方法傳遞過來的資料,onRejected用於接收reject方法傳遞過來的資料。

let p = function () {
    return new Promise(function (resolve, reject) {
        setTimeout(() => {
            if (Math.random() > 0.5) {
                resolve('OK');
            } else {
                reject('ERR');
            }
        }, 1000);
    });
};

p().then(function (data) {
    console.log('fulfilled', data);
}, function (err) {
    console.log('rejected', err);
});

then()方法總是會返回一個Promise例項,這樣我們就可以一直呼叫then()。

在then方法中,你既可以return 一個具體的值 ,還可以return 一個Promise物件。

如果直接return的是一個數據,那then方法會返回一個新Promise物件,並以該資料進行resolve。

let p = function () {
    return new Promise(function (resolve, reject) {
        resolve(1);
    });
};

p().then(function (data) {
    console.log(`第 ${data} 次呼叫`);
    //注意這裡直接返回的值
    //then會建立一個新的Promise物件,並且以返回的值進行resolve
    //那麼該值會被下面的then方法的onFulfilled回撥拿到
    return ++data;
}).then(function (data) {
    console.log(`第 ${data} 次呼叫`);
    return ++data;
}).then(function (data) {
    console.log(`第 ${data} 次呼叫`);
    return ++data;
});

如果返回的是一個Promise物件,請看下面程式碼。

let p = function () {
    return new Promise(function (resolve, reject) {
        resolve(1);
    });
};

p().then(function (data) {
    console.log(`第 ${data} 次呼叫`);

    return new Promise(function (resolve, reject) {
        resolve(++data);
    });
}).then(function (data) {
    console.log(`第 ${data} 次呼叫`);

    return new Promise(function (resolve, reject) {
        resolve(++data);
    });
}).then(function (data) {
    console.log(`第 ${data} 次呼叫`);

    return new Promise(function (resolve, reject) {
        resolve(++data);
    });
});

其實效果與直接返回值的是一樣的。

即然then()可以進行鏈式操作,那我們最早之前的回撥地獄寫法,就可以通過它進行改進了。

function login() {
    return new Promise(function (resolve, reject) {
        $.get('/login.php', function (result) {
            resolve(result);
        });
    });
}

function user(data) {
    return new Promise(function (resolve, reject) {
        $.get('/user.php', function (result) {
            resolve(result);
        });
    });
}

function info(data) {
    return new Promise(function (resolve, reject) {
        $.get('/info.php', function (result) {
            resolve(result);
        });
    });
}

login().then(function (data) {
    console.log('處理login');
    //把login非同步處理獲取的資料,傳入到下一個處理中。
    return user(data);
}).then(function (data) {
    console.log('處理user');
    //把user非同步處理獲取的資料,傳入到下一個處理中。
    return info(data);
}).then(function (data) {
    console.log('處理info');
});

這樣修改後,回撥地獄層層巢狀的結構就變的清晰多了。上述程式碼是虛擬碼。

Promise物件還有一個catch方法,用於捕獲錯誤,該方法與 then(null, onRejected) 等同,是一個語法糖。

let p = function () {
    return new Promise(function (resolve, reject) {
        resolve('開始');
    });
};

p().then(function (data) {
    console.log('1');
    return new Promise(function (resolve, reject) {
        reject('錯誤1');
    });
}).then(function (data) {
    console.log('2');
    return new Promise(function (resolve, reject) {
        reject('錯誤2');
    });
}).then(function (data) {
    console.log('3');
    return new Promise(function (resolve, reject) {
        reject('錯誤3');
    });
}).catch(function (reason) {
    console.log(reason);
});

注意,一旦操作中有錯誤發生,則會進入到catch中,後面的操作將不再執行。

Promise物件內部自帶了try catch,當代碼執行時錯誤,會自動以錯誤物件為值reject,並最終被catch捕獲。

let p = function () {
    return new Promise(function (resolve, reject) {
        resolve('開始');
    });
};

p().then(function (data) {
    //注意這裡列印了一個未定義的變數
    console.log(a);
}).catch(function (reason) {
    //這裡會捕獲到錯誤
    console.log('rejected');
    console.log(reason);
});

Promise還提供了,all(),race(),reject(),resolve()等在建構函式上的方法,呼叫這些方法並不需要例項化物件。

all()方法,可以讓我們並行的執行非同步操作,直到所有操作完成了,才執行回撥。

function fn1() {
    return new Promise(function (resolve, reject) {
        setTimeout(function () {
            resolve('fn1');
        }, 1000);
    });
}

function fn2() {
    return new Promise(function (resolve, reject) {
        setTimeout(function () {
            resolve('fn2');
        }, 2000);
    });
}

function fn3() {
    return new Promise(function (resolve, reject) {
        setTimeout(function () {
            resolve('fn3');
        }, 3000);
    });
}

//all會等待所有操作完成,會把所有操作的結果放到一個數組中,傳給then。
Promise.all([fn1(), fn2(), fn3()]).then(function (data) {
    console.log(data);
});

race()方法是誰先處理完,就以誰為準,把最先處理完的結果傳給then。

function fn1() {
    return new Promise(function (resolve, reject) {
        setTimeout(function () {
            resolve('fn1');
        }, 1000);
    });
}

function fn2() {
    return new Promise(function (resolve, reject) {
        setTimeout(function () {
            resolve('fn2');
        }, 2000);
    });
}

function fn3() {
    return new Promise(function (resolve, reject) {
        setTimeout(function () {
            resolve('fn3');
        }, 3000);
    });
}

//race是以誰先處理完,就以誰為準,fn1最先處理完,那fn1的結果會傳給then
//注意這裡fn2和fn3還是會執行,不過不會進入then了
Promise.race([fn1(), fn2(), fn3()]).then(function (data) {
    console.log(data);
});

reject()方法,返回一個帶有拒絕原因reason引數的Promise物件。

// Promise.reject('錯誤') 
// 等同於
// new Promise(function(resolve, reject) {
//     reject('錯誤');
// });
let p = Promise.reject('錯誤');

p.then(function (data) {

}).catch(function (reason) {
    console.log(reason);
});

resolve()方法,根據傳入的值返回一個Promise物件。

//如果傳入的引數是普通值,則返回一個新Promise物件,並以該值resolve
let p1 = Promise.resolve('OK');
p1.then(function (data) {
    console.log(data);
});

//如果傳入的引數是一個Promise物件,則原封不動的返回該Promise物件
let obj = new Promise(function (resolve, reject) {
    resolve('我是Promise物件');
});
let p2 = Promise.resolve(obj);
p2.then(function (data) {
    console.log(data);
    console.log(p2 === obj);
});

//如果傳入的引數是一個thenable物件(帶有then方法),
//會轉換成Promise物件,並執行thenable物件的then方法
let then = {
    then(resolve, reject) {
        resolve('我是thenable物件');
    }
}
let p3 = Promise.resolve(then);
p3.then(function (data) {
    console.log(data);
});

//如果什麼引數都不傳入,則返回狀態為resolved的Promise物件
let p4 = Promise.resolve();
p4.then(function (data) {
    console.log(data);
}).catch(function (reason) {
    console.log(reason);
});