Promise必知必會

前端開發中常常會進行一些異步操做,常見的異步有:javascript

  • 網絡請求:ajax
  • IO操做: readFile
  • 定時器:setTimeout

博客地址php

回調

最基礎的異步解決方案莫過於回調函數了html

前端常常會在成功時和失敗時分別註冊回調函數前端

const req = new XMLHttpRequest();
req.open('GET', URL, true);
req.onload = function () {
    // 成功的回調
    if (req.status === 200) {
        console.log(req.statusText)
    }
};
req.onerror = function () {
    // 失敗的回調
    console.log(req.statusText)
};
req.send();

node的異步api,則一般只註冊一個回調函數,經過約定的參數來判斷究竟是成功仍是失敗:java

const fs = require("fs");
fs.readFile('input.txt', function (err, data) {
    // 回調函數
    // 第一個參數是err,若是有err,則表示調用失敗
   if (err) {
       return console.error(err);
   }
   console.log("異步讀取: " + data.toString());
});

回調的異步解決方案自己也簡單易懂,可是它有一個致命的缺點:沒法優雅的控制異步流程node

什麼意思?git

單個異步固然能夠很簡單的使用回調函數,可是對於多個異步操做,就會陷入回調地獄中es6

// 請求data1成功後再請求data2,最後請求data3
const ajax = $.ajax({
    url: 'data1.json',
    success: function(data1) {
        console.log(data1);
        $.ajax({
            url: 'data2.json',
            success: function(data2) {
                console.log(data2);
                $.ajax({
                    url: 'data3.json',
                    success: function(data3) {
                        console.log(data3);

                    }
                })
            }
        })
    }
})

這種要按順序進行異步流程控制的場景,回調函數就顯得捉襟見肘了。這時,Promise的異步解決方案就被提了出來。github

Promise

當初在學Promise時,看得我真是一臉懵逼,徹底不明白這貨到底怎麼用。其實,Promise的api要分紅兩部分來理解:ajax

  1. Promise構造函數:resolve reject (改變內部狀態)
  2. Promise對象: then catch (流程控制)

Promise對象

Promise對象表明一個異步操做,有三種狀態:pending(進行中)、fulfilled(已成功)和rejected(已失敗)

初始時,該對象狀態爲pending,以後只能變成fulfilled和rejected其中的一個

then方法有兩個參數,分別對應狀態爲fulfilled和rejected時的回調函數,其中第二個參數可選

promise.then(function(value) {
  // success
}, function(error) {
  // failure
});

一般咱們會省略then的第二個參數,而改用catch來註冊狀態變爲rejected時的回調函數

promise.then(function(value) {
  // success
}).catch(function(error) {
  // failure
});

Promise構造函數

Promise對象怎麼生成的呢?就是經過構造函數new出來的。

const promise = new Promise(function(resolve, reject) {
    
});

Promise構造函數接收一個函數做爲參數,這個函數能夠接收兩個參數:resolve和reject

resolve, reject是兩個函數,由JavaScript引擎提供,不用本身編寫

前面咱們說過,Promise對象有三種狀態,初始時爲pending,以後能夠變成fulfilled或者rejected,那怎麼改變狀態呢?答案就是調用resolve或者reject

調用resolve時,狀態變成fulfilled,表示異步已經完成;調用reject時,狀態變成rejected,表示異步失敗。

回調和Promise的對比

其實這裏就是Promise最難理解的地方了,咱們先看下例子:

回調函數封裝

function getURL(URL, success, error) {
    const req = new XMLHttpRequest();
    req.open('GET', URL, true);
    req.onload = function () {
        if (req.status === 200) {
            success(req.responseText);
        } else {
            error(new Error(req.statusText));
        }
    };
    req.onerror = function () {
        error(new Error(req.statusText));
    };
    req.send();
}
const URL = "http://httpbin.org/get";
getURL(URL, function onFulfilled(value) {
    console.log(value);
}, function onRejected(error) {
    console.error(error);
})

Promise封裝

function getURL(URL) {
    return new Promise(function (resolve, reject) {
        const req = new XMLHttpRequest();
        req.open('GET', URL, true);
        req.onload = function () {
            if (req.status === 200) {
                resolve(req.responseText);
            } else {
                reject(new Error(req.statusText));
            }
        };
        req.onerror = function () {
            reject(new Error(req.statusText));
        };
        req.send();
    });
}

const URL = "http://httpbin.org/get";
getURL(URL).then(function onFulfilled(value){
    console.log(value);
}).catch(function onRejected(error){
    console.error(error);
});

兩段代碼最大的區別就是:

用回調函數封裝的getURL函數,須要明顯的傳給它成功和失敗的回調函數,success和error的最終調用是在getURL裏被調用的

用Promise封裝的getURL函數,徹底不關心成功和失敗的回調函數,它只須要在ajax成功時調用resolve(),告訴promise對象,你如今的狀態變成了fulfilled,在ajax失敗時,調用reject()。而真正的回調函數,是在getURL的外面被調用的,也就是then和catch中調用

then方法返回的是一個新的Promise實例(注意,不是原來那個Promise實例)。所以能夠採用鏈式寫法,即then方法後面再調用另外一個then方法。

function getURL(URL) {
    return new Promise(function (resolve, reject) {
        const req = new XMLHttpRequest();
        req.open('GET', URL, true);
        req.onload = function () {
            if (req.status === 200) {
                resolve(req.responseText);
            } else {
                reject(new Error(req.statusText));
            }
        };
        req.onerror = function () {
            reject(new Error(req.statusText));
        };
        req.send();
    });
}

const URL = "http://httpbin.org/get";
const URL2 = "http://deepred5.com/cors.php?search=ntr";
getURL(URL).then(function onFulfilled(value){
    console.log(value);
    // 返回了一個新的Promise對象
    return getURL(URL2)
}).then(function onFulfilled(value){
    console.log(value);
}).catch(function onRejected(error){
    console.error(error);
});

這段代碼就充分說明了Promise對於流程控制的優點:讀取URL的數據後再讀取URL2,沒有了以前的回調地獄問題。

Promise應用

Promise常常用於對函數的異步流程封裝

function getURL(URL) {
    return new Promise(function (resolve, reject) {
        const req = new XMLHttpRequest();
        req.open('GET', URL, true);
        req.onload = function () {
            if (req.status === 200) {
                resolve(req.responseText);
            } else {
                reject(new Error(req.statusText));
            }
        };
        req.onerror = function () {
            reject(new Error(req.statusText));
        };
        req.send();
    });
}
const preloadImage = function (path) {
  return new Promise(function (resolve, reject) {
    const image = new Image();
    image.onload  = resolve;
    image.onerror = reject;
    image.src = path;
  });
};
const fs = require('fs')
const path = require('path') 
const readFilePromise = function (fileName) {
    return new Promise((resolve, reject) => {
        fs.readFile(fileName, (err, data) => {
            if (err) {
                reject(err)
            } else {
                resolve(data.toString())
            }
        })
    })
}

結合上面幾個例子,咱們能夠看出Promise封裝代碼的基本套路:

const methodPromise = function() {
    return new Promise((resolve, reject) => {
        // 異步流程
        if (/* 異步操做成功 */){
            resolve(value);
        } else {
            reject(error);
        }
    })
}

Promise.race Promise.all

Promise.all 接收一個promise對象的數組做爲參數,當這個數組裏的全部promise對象所有變爲resolve的時候,它纔會去調用then方法,若是其中有一個變爲rejected,就直接調用catch方法

傳給then方法的是一個數組,裏面分別對應promise返回的結果

function getURL(URL) {
    return new Promise(function (resolve, reject) {
        const req = new XMLHttpRequest();
        req.open('GET', URL, true);
        req.onload = function () {
            if (req.status === 200) {
                resolve(req.responseText);
            } else {
                reject(new Error(req.statusText));
            }
        };
        req.onerror = function () {
            reject(new Error(req.statusText));
        };
        req.send();
    });
}

Promise.all([getURL('http://deepred5.com/cors.php?search=ntr'), getURL('http://deepred5.com/cors.php?search=rbq')])
.then((dataArr) => {
    const [data1, data2] = dataArr;
}).catch((err) => {
    console.log(err)
})

Promise.race相似,只不過只要有一個Promise變成resolve就調用then方法

Promise.resolve Promise.reject

Promise.resolve(42); 
// 等價於
new Promise(function(resolve){
    resolve(42);
});

Promise.reject(new Error("出錯了"))
// 等價於
new Promise(function(resolve,reject){
    reject(new Error("出錯了"));
});
Promise.resolve(42).then(function(value){
    console.log(value);
});

Promise.reject(new Error("出錯了")).catch(function(error){
    console.error(error);
});

Promise.resolve方法另外一個做用就是將thenable對象轉換爲promise對象

const promise = Promise.resolve($.ajax('/json/comment.json'));// => promise對象
promise.then(function(value){
   console.log(value);
});

thenable對象指的是具備then方法的對象:

let thenable = {
  then: function(resolve, reject) {
    resolve(42);
  }
};
let p1 = Promise.resolve(thenable);
p1.then(function(value) {
  console.log(value);  // 42
});

異常捕獲

理想狀態下,Promise能夠經過catch捕獲到異常,可是若是咱們沒有使用catch,那麼雖然控制檯會打印錯誤,可是此次錯誤並不會終止腳本執行

<script>
const a = b.c.d;
console.log(1); // 代碼報錯,不會運行到此處
</script>
<script>
console.log(2); // 代碼運行
</script>

上述代碼只會打印2

<script>
const promise = new Promise((resolve, reject) => {
    const a = b.c.d;
    resolve('ok');
})
promise.then(data => {
    console.log(data)
})
console.log(1); // 代碼報錯,可是會運行到此處
</script>
<script>
console.log(2); // 代碼運行
</script>

打印1和2

解決方法:
window有一個unhandledRejection事件,專門監聽未捕獲的reject錯誤

window.onunhandledrejection = function(e) {
    console.log(e.reason);
}
const promise = new Promise((resolve, reject) => {
    const a = b.c.d;
    resolve('ok');
})
promise.then(data => {
    console.log(data)
})

參考

相關文章
相關標籤/搜索