「ES6系列」完全弄懂Promise(內附習題)

Promise 出現的緣由

曾幾什麼時候,咱們爲了獲取異步調用的結果,不得不大量使用回調函數,咱們看下面這個例子:javascript

經過Jquery的ajax獲取服務器數據前端

let url1 = 'http://xxx.xxx.1';
$.ajax({
    url:url1,
    error:function (error) {},
    success:function (data1) {
        console.log(data1);
    }
});
複製代碼

當須要發送多個異步請求,且每一個請求之間須要相互依賴時,咱們只能以嵌套的方式來解決java

let url1 = 'http://xxx.xxx.1';
let url2 = 'http://xxx.xxx.2';
let url3 = 'http://xxx.xxx.3';
$.ajax({
    url:url1,
    error:function (error) {},
    success:function (data1) {
        console.log(data1);
        $.ajax({
            url:url2,
            data:data1,
            error:function (error) {},
            success:function (data2) {
                console.log(data2);
                $.ajax({
                    url:url3,
                    data,
                    error:function (error) {},
                    success:function (data3) {
                        console.log(data3);
                    }
                });
            }
        });
    }
});
複製代碼

在處理多個異步邏輯時,就須要多層的回調函數嵌套,也就是咱們常說的回調地獄。node

這種編碼模式的主要問題:git

  • 代碼臃腫。
  • 可讀性差。
  • 耦合度太高,可維護性差。
  • 代碼複用性差。
  • 容易滋生 bug。
  • 只能在回調裏處理異常。

而Promise的出現,就是爲了解決回調函數所引出的各類問題github

什麼是Promise

Promise 是異步編程的一種解決方案,比傳統的異步解決方案【回調函數】和【事件】更合理、更強大。ajax

  • 從語法上講,promise 是一個對象,從它能夠獲取異步操做的消息
  • 從本意上講,它是承諾,承諾它過一段時間會給你一個結果

代碼書寫比較

首先封裝一個支持 Promise 的 ajax 方法,不理解這塊代碼的話,也沒有關係,後文會逐步講解 Promise 的執行機制編程

function request(url,data = {}){
    return new Promise((resolve,reject)=>{
        $.ajax({
            url,
            data,
            success:function (data) {
                resolve(data);
            },
            error:function (error) {
                reject(error);
            }
        })
    });
}
複製代碼

用 request 方法實現前面多個互相依賴的網絡請求數組

let url1 = 'http://xxx.xxx.1';
let url2 = 'http://xxx.xxx.2';
let url3 = 'http://xxx.xxx.3';
request(url1)
    .then((data)=>{
        console.log(data);
        return request(url2,data)
    })
    .then((data)=>{
        console.log(data);
        return request(url3,data)
    })
    .then((data)=>{
        console.log(data)
    })
    .catch((error)=>{
        console.log(error);
    });

複製代碼

Promise 的特性

promise 的狀態

  • pending (等待態)
  • fulfilled (完成態)
  • rejected (拒絕態)

終值與拒因

  • 終值:指的是 promise 被解決時傳遞給解決回調的值
  • 拒因:拒絕緣由,指在 promise 被拒絕時傳遞給異常回調的拒絕緣由

狀態與狀態關係,狀態與終值和拒因的關係

  • pending 能夠遷移至 fulfilled 或 rejected
  • fulfilled 不能遷移至其餘狀態,必須擁有一個不可變的終值
  • rejected 不能遷移至其餘狀態,必須擁有一個不可變的據因

Promise 的使用

構造函數

Promise 是一個構造函數,使用 new 操做符返回一個 promise 對象promise

構造函數接收一個 excutor 函數做爲參數

excutor 函數有兩個函數類型的參數 resolve 和 reject

let p = new Promise((resolve,reject)=>{
     // 在 excutor 函數中執行異步操做
     // 當異步操做完成後,調用 resolve 函數或 reject 函數
});
複製代碼
  • 構造函數在調用時,excutor 函數會做爲同步代碼當即執行
  • 咱們一般在 excutor 函數中執行咱們的異步操做
  • 未調用 resolve、reject 函數時,promise 對象的狀態爲 pending
let p1 = new Promise((resolve,reject)=>{
    setTimeout(()=>{
        console.log('p1');
    },1000);
});
// p1 的狀態一直爲 pending
複製代碼
  • 當調用 resolve 函數, resolve 的參數爲非 promise 對象、非 thenable 對象
    • resolve 函數的參數,做爲 promise 對象的終值
    • promise 對象的狀態變爲 fulfilled
let p2 = new Promise((resolve,reject)=>{
    setTimeout(()=>{
        console.log('p2');
        resolve('我是p2的終值')
    },1000);
});
// 代碼執行,1000ms內,p2 的狀態爲 pending
// 代碼執行,1000ms後,p2 的狀態爲 fulfilled
// 代碼執行,1000ms後,p2 的終值爲 '我是p2的終值'
複製代碼
  • 當調用 resolve 函數, resolve 的參數爲 promise 對象
    • promise 對象的狀態、終值、拒因與傳入的 promise 對象同步
let p = new Promise((resolve,reject)=>{
    reject('error')
})
let p1 = new Promise((resolve,reject)=>{
    resolve(p)
})
// p1 的狀態爲 rejected ,拒由於 error
複製代碼
  • 當調用 resolve 函數, resolve 的參數爲 thenable 對象
    • 會對 thenable 對象進行展開操做,promise 對象的狀態、終值、拒因取決於 thenable 對象的 then 方法調用結果
let thenable1 = {
    then:function(resolve,reject){
        resolve(1)
    }
}
let thenable2 = {
    then:function(resolve,reject){
        reject(2)
    }
}
let thenable3 = {
    then:function(resolve,reject){
        throw new Error(3)
    }
}
let thenable4 = {
    then:function(fn1,fn2){
        //不調用 fn1 fn2
    }
}
let p1 = new Promise((resolve,reject)=>{
    resolve(thenable1);
})
let p2 = new Promise((resolve,reject)=>{
    resolve(thenable2);
})
let p3 = new Promise((resolve,reject)=>{
    resolve(thenable3);
})
let p4 = new Promise((resolve,reject)=>{
    resolve(thenable4);
})
// p1 的狀態爲 fulfilled 終值爲 1
// p2 的狀態爲 rejected 終值爲 2
// p3 的狀態爲 rejected 拒由於 Error:3
// p4 的狀態爲 pending
複製代碼
  • 當調用 reject 函數, reject 函數的參數,做爲 promise 對象的拒因
  • promise 對象的狀態變爲 rejected
let p3 = new Promise((resolve,reject)=>{
    setTimeout(()=>{
        console.log('p3');
        reject('我是p3的拒因')
    },1000);
});
// 代碼執行,1000ms內,p3 的狀態爲 pending
// 代碼執行,1000ms後,p3 的狀態爲 rejected
// 代碼執行,1000ms後,p3 的拒由於 '我是p3的拒因'
複製代碼

promise對象上的方法

then方法:

promise 提供一個 then 方法,用於訪問其終值和拒因。

promise 的 then 方法接受兩個參數:

promise.then(onFulfilled, onRejected);
複製代碼
  • onFulfilled 函數用於當 promise 狀態變爲 fulfilled 時,接收終值
  • onRejected 函數用於當 promise 狀態變爲 rejected 時,接收拒因
new Promise((resolve,reject)=>{
    setTimeout(()=>{
        resolve('異步任務獲取的數據')
    },50)
}).then((data)=>{
    console.log(data)
})
// 異步任務獲取的數據
複製代碼
new Promise((resolve,reject)=>{
    setTimeout(()=>{
        reject(new Error('異步任務異常'))
    },50)
}).then(null,(error)=>{
    console.log(error)
})
// Error: 異步任務異常
複製代碼
new Promise((resolve,reject)=>{
    throw new Error('拋出一個異常');
}).then(null,(error)=>{
    console.log(error)
})
// Error: 拋出一個異常
複製代碼
onFulfilled 和 onRejected 參數可選
  • 若是 onFulfilled 不是函數,其必須被忽略
  • 若是 onRejected 不是函數,其必須被忽略
onFulfilled 特性

若是 onFulfilled 是函數:

  • 當 promise 執行結束後其必須被調用,其第一個參數爲 promise 的終值
  • 在 promise 執行結束前其不可被調用
  • 其調用次數不可超過一次
onRejected 特性

若是 onRejected 是函數:

  • 當 promise 被拒絕執行後其必須被調用,其第一個參數爲 promise 的據因
  • 在 promise 被拒絕執行前其不可被調用
  • 其調用次數不可超過一次
onFulfilled 和 onRejected 的調用時機
  • 當 promise 對象的狀態變爲 fulfilled 或 rejected 時調用
  • onFulfilled、onRejected 永遠都是異步調用
  • onFulfilled、onRejected 在事件隊列中做爲微任務來處理
console.log(1);
setTimeout(function(){
    console.log(2)
},0)
new Promise((resolve,reject)=>{
    resolve(3);
}).then((data)=>{
    console.log(data);
})
console.log(4)
// print: 1 4 3 2
複製代碼
onFulfilled 和 onRejected 的調用要求
  • onFulfilled 和 onRejected 必須被做爲函數調用
  • 非嚴格模式下,this 爲全局對象
  • 嚴格模式下,this 爲 undefined
function fn1(){
    new Promise((resolve)=>{
        resolve();
    }).then(function(){
        console.log(this)
    })
}
function fn2(){
 "use strict";
    new Promise((resolve)=>{
        resolve();
    }).then(function(){
        console.log(this)
    })
}
fn1(); // print: window
fn2(); // print: undefined
複製代碼
then方法的屢次調用
  • then 方法能夠被同一個 promise 對象屢次調用
  • then 方法會返回一個新的 promise 對象
  • 當 promise 成功執行時,全部 onFulfilled 需按照其註冊順序依次回調
  • 當 promise 被拒絕執行時,全部的 onRejected 需按照其註冊順序依次回調
let p = new Promise((resolve)=>{
    resolve()
});
let p1 = p.then(()=>{
    console.log('異步執行,第一個onFulfilled');
});
let p2 = p.then(()=>{
    console.log('異步執行,第二個onFulfilled');
});
console.log(p1.constructor === Promise);
console.log(p === p1);
console.log(p === p2);
console.log(p1 === p2);
// print: true
// print: false
// print: false
// print: false
// print: 異步執行,第一個onFulfilled
// print: 異步執行,第二個onFulfilled
複製代碼
then方法的返回值

then 方法返回一個 promise 對象

promise2 = promise1.then(onFulfilled, onRejected);   
複製代碼
  • 若 onFulfilled 、onRejected 返回一個非promise對象、非thenable對象的值 x ,則 promise2 的狀態爲 fulfilled ,終值爲 x
let p = new Promise((resolve,reject)=>{
    throw new Error();
});
let p1 = p.then(null,(data)=>{
    return '我是p2的終值'
});
p1.then((data)=>{
    console.log(data)
});
// print: 我是p2的終值
複製代碼
  • 若 onFulfilled 、onRejected 返回一個 promise 對象的值 x ,promise2 的狀態、終值、拒因與 x 同步
let p1 = new Promise((resolve,reject)=>{
    resolve(1)
})
let p2 = new Promise((resolve,reject)=>{
    reject(2)
})
let p3 = new Promise((resolve)=>{
    resolve()
})
let p4 = p3.then(()=>{
    return p1;
})
let p5 = p3.then(()=>{
    return p2;
})
// p4 的狀態爲 fulfilled 終值爲 1
// p5 的狀態爲 rejected 拒由於 2
複製代碼
  • 若 onFulfilled 、onRejected 返回一個 thenable 對象 ,會對 thenable 對象進行展開操做,promise2 的狀態、終值、拒因取決於 thenable 對象的 then 方法調用結果
let thenable1 = {
    then:function(resolve,reject){
        resolve(1)
    }
}
let thenable2 = {
    then:function(resolve,reject){
        reject(2)
    }
}
let p1 = new Promise((resolve,reject)=>{
    resolve()
})
let p2 = p1.then(()=>{
    return thenable1;
})
let p3 = p1.then(()=>{
    return thenable2;
})
// p2 的狀態爲 fulfilled 終值爲 1
// p3 的狀態爲 rejected 拒由於 2
複製代碼
  • 若 onFulfilled 或者 onRejected 拋出一個異常 e ,則 promise2 的狀態爲 rejected,拒由於 e
let p = new Promise((resolve,reject)=>{
    resolve();
});
let p1 = p.then((data)=>{
    throw new Error('error')
});
p1.then(null,(err)=>{
    console.log(err);
});
// print: Error: error
複製代碼
  • 若 onFulfilled 不是函數且 promise1 成功執行, promise2 的狀態爲 fulfilled 終值爲 promise1 的終值
let p = new Promise((resolve,reject)=>{
    resolve('我是p1的終值');
});
let p1 = p.then(null,null);
p1.then((data)=>{
    console.log(data);
});
// print: 我是p1的終值
複製代碼
  • 若 onRejected 不是函數且 promise1 拒絕執行, promise2 的狀態爲 rejected 拒由於 promise1 的拒因
let p = new Promise((resolve,reject)=>{
    reject('我是p1的拒因');
});
let p1 = p.then(null,null);
p1.then(null,(err)=>{
    console.log(err);
});
// print:我是p1的拒因
複製代碼
  • 若 onFulfilled、onRejected 執行過程當中拋出異常,則 promise2 的狀態爲 rejected 拒由於拋出的異常
let p = new Promise((resolve,reject)=>{
    resolve('我是p的終值');
});
let p1 = p.then((data)=>{
    throw new Error('異常')
});
p1.then(null,(err)=>{
    console.log(err);
});
// print:Error: 異常
複製代碼
終值和拒因的穿透特性
  • 若是 promise 的狀態變爲 fulfilled,then 方法沒有註冊 onFulfilled
    • then 方法返回的 promise 對象的狀態變爲 fulfilled
    • then 方法返回的 promise 對象的終值與原 promise 對象的終值相同
  • 若是 promise 的狀態變爲 rejected,then 方法沒有註冊 onRejected
    • then 方法返回的 promise 對象的狀態變爲 rejected
    • then 方法返回的 promise 對象的拒因與原 promise 對象的拒因相同
let p1 = new Promise((resolve,reject)=>{
    resolve(1)
})
let p2 = new Promise((resolve,reject)=>{
    reject(2)
})

let p3 = p1.then(null,null);
let p4 = p2.then(null,null);
// p3 的狀態是 fulfilled 終值 1
// p4 的狀態是 rejected 拒因 2


p5 = p3.then(null,null);
p6 = p4.then(null,null);
// p3 的狀態是 fulfilled 終值 1
// p4 的狀態是 rejected 拒因 2
複製代碼
  • 穿透特性主要用於異常處理
let fn1 = function(){}
let fn2 = function(){}
let fn3 = function(){}
let fn4 = function(){}
let fn5 = function(){}
let onError = function(){};
new Promise((resolve,reject)=>{
    setTimeout(function(){
        reject()
    })
})
.then(fn1)
.then(fn2)
.then(fn3)
.then(fn4)
.then(fn5)
.then(null,onError)
複製代碼

fn一、fn二、fn三、fn四、fn5 均可能發生錯誤,經過在最後的then函數註冊的 onRejected 函數接收可能發生異常錯誤

catch方法:

catch(fn) 方法其實是 then(null,fn) 方法的別名,catch 方法的返回值以及 catch 方法中出現異常的狀況與調用 then 方法相同

new Promise((resolve,reject)=>{
    reject()
}).then(null,function(error){

})
// 等同於
new Promise((resolve,reject)=>{
    reject()
}).catch(function(error){

})
複製代碼

Promise 的靜態方法

Promise.resolve

  • Promise.resolve 方法用於將現有數據轉換爲 promise 對象
    • 若入參爲 promise 對象
      • 返回的 promise 對象的狀態、終值、拒因與 Promise.resolve 方法的入參同步
    • 若入參爲 thenable 對象
      • 會對 thenable 對象進行展開操做,返回的 promise 對象的狀態、終值、拒因取決於 thenable 對象的 then 方法調用結果
    • 若入參爲非 promise 非 thenable 對象
      • 返回的 promise 對象的狀態爲 fulfilled
      • 返回的 promise 對象的終值爲 Promise.resolve 方法的入參
let p = Promise.resolve(x)
// 等價於
let p = new Promise((resolve)=>{
    resolve(x)
})
複製代碼

Promise.reject

  • Promise.reject 方法用於返回一個狀態爲 rejected ,拒由於方法入參的 promise 對象
let p = Promise.reject(x)
// 等價於
let p = new Promise((resolve,reject)=>{
    reject(x)
})
複製代碼

Promise.all

  • Promise.all 方法用於將多個 promise 對象包裝成一個新的 promise 對象
const p = Promise.all([p1, p2, p3]);
複製代碼
  • p一、p二、p3 都是 promise 對象,若是不是,調用 Promise.resolve 方法轉換爲 promise 對象
  • p 的狀態由 p一、p二、p3 決定
    • 當 p一、p二、p3 的狀態都變成 fulfilled
      • p 的狀態爲 fulfilled
      • 此時 p一、p二、p3 的終值組成一個數組,這個數組做爲 p 的終值
    • 當 p一、p二、p3 的狀態有一個變成 rejected
      • p 的狀態變爲 rejected
      • 此時第一個狀態變爲 rejected 的 promise 對象的拒因做爲 p 的拒因
let p1 = Promise.resolve(1);
let p2 = Promise.resolve(2);
let p3 = 3;

Promise.all([p1,p2,p3]).then((data)=>{
    console.log(data); // print: [1,2,3]
})
複製代碼
let p1 = Promise.resolve(1);
let p2 = new Promise((resolve,reject)=>{
    setTimeout(function(){
        reject('p2 error')
    },1000)
})
let p3 = new Promise((resolve,reject)=>{
    setTimeout(function(){
        reject('p3 error')
    },500)
})
Promise.all([p1,p2,p3]).catch((error)=>{
    console.log(error); // print: p3 error
})
複製代碼

Promise.race

  • Promise.race 方法一樣用於將多個 promise 對象包裝成一個新的 promise 對象
const p = Promise.race([p1, p2, p3]);
複製代碼
  • p一、p二、p3 都是 promise 對象,若是不是,調用 Promise.resolve 方法轉換爲 promise 對象
  • p 的狀態由 p一、p二、p3 中狀態最早變爲 fulfilled 或 rejected 的 promise 對象決定
  • p 的終值或拒因由最早變動狀態的 promise 對象所決定
let p1 = Promise.resolve(1);
let p2 = new Promise((resolve,reject)=>{
    setTimeout(function(){
        reject('p2 error')
    },1000)
})
let p3 = new Promise((resolve,reject)=>{
    setTimeout(function(){
        reject('p3 error')
    },500)
})
Promise.race([p1,p2,p3]).then(data=>{
    console.log(data);
}).catch(error=>{
    console.log(error);
})
// print: 1
複製代碼
let p1 = new Promise((resolve,reject)=>{
    setTimeout(function(){
        resolve(1)
    },1000)
})
let p2 = new Promise((resolve,reject)=>{
    setTimeout(function(){
        reject('p2 error')
    },800)
})
let p3 = new Promise((resolve,reject)=>{
    setTimeout(function(){
        reject('p3 error')
    },500)
})

Promise.race([p1,p2,p3]).then(data=>{
    console.log(data);
}).catch(error=>{
    console.log(error);
})
// print: p3 error
複製代碼

Promise 的錯誤捕獲

當 promise 的狀態爲 rejected 且爲對 promise 對象使用 catch 方法,此時的異常信息會被 promise 對象吃掉 能夠經過監聽 unhandledRejection 事件,專門監聽未捕獲的reject錯誤

// node 環境下
process.on('unhandledRejection', error => {
    console.log('unhandledRejection', error);
});
// 瀏覽器下
window.addEventListener('unhandledrejection',(e)=>{
    e.preventDefault();
    console.log(e);
});
複製代碼

Promise 的問題

  • 沒法取消Promise,若沒有狀態變動,也沒法中止 promise 的等待
  • 不設定 then 或 catch 方法,構造函數(excutor函數)錯誤,沒法捕獲
  • 未完成狀態時,沒法得知是剛開始,仍是即將完成

Promise 題目

題目一

const promise = new Promise((resolve, reject) => {
  console.log(1)
  resolve()
  console.log(2)
})
promise.then(() => {
  console.log(3)
})
console.log(4)
複製代碼

結果:1 2 4 3

題目二

const promise = new Promise((resolve, reject) => {
  resolve('success1')
  reject('error')
  resolve('success2')
})

promise
  .then((data) => {
    console.log(data)
  })
  .catch((err) => {
    console.log(err)
  })
複製代碼

結果:success1

題目三

Promise.resolve(1)
  .then((data) => {
    console.log(data)
    return 2
  })
  .catch((err) => {
    return 3
  })
  .then((data) => {
    console.log(data)
  })
複製代碼

結果:1 2

題目四

Promise.resolve(1)
  .then(2)
  .then(Promise.resolve(3))
  .then(console.log)
複製代碼

結果:1

題目五

new Promise((resolve,reject)=>{
    console.log(3);
    let p = new Promise((resolve, reject)=>{
        console.log(7);
        setTimeout(()=>{
           console.log(5);
           resolve(6); 
        },0)
        resolve(1);
    });
    resolve(2);
    p.then((arg)=>{
        console.log(arg);
    });

}).then((arg)=>{
    console.log(arg);
});
console.log(4);
複製代碼

結果:3 7 4 1 2 5

以上題目,若有疑問,可在留言區與我互動詢問

參考

系列文章推薦

寫在最後

  • 文中若有錯誤,歡迎在評論區指正,若是這篇文章幫到了你,歡迎點贊關注
  • 本文同步首發與github,可在github中找到更多精品文章,歡迎Watch & Star ★
  • 後續文章參見:計劃

歡迎關注微信公衆號【前端小黑屋】,每週1-3篇精品優質文章推送,助你走上進階之旅

相關文章
相關標籤/搜索