聊聊promise系列(基礎)

前言

本文將從淺到深的去剖析promise。因爲內容較多,分爲上下兩篇。javascript

內容大綱

  • 異步編程的解決方案(完成)
  • promise是什麼(完成)
  • promise的用法(完成)
  • promise的優勢與缺點(完成)
  • promise的錯誤捕獲(待續)
  • promise的原理(待續)
  • promise的底層源碼(待續)
  • 本身實現一個promise(待續)

異步編程的解決方案

js是一門單線程的語言,因此其中會涉及到不少異步的操做,異步編程的解決方案有不少種,咱們主要講一種最基礎的和這篇文章的主角(promise):java

回調函數

在異步編程中,這種的應用範圍最廣,舉個定時器的例子:編程

setTimeout(() => {
    // ...
}, 1000);
複製代碼

固然在業務中異步請求也會用到不少,但當多個異步操做須要串行操做的時候,就會有回調地獄產生。promise

$.get(url, data1 => {
    console.log(data1)
    $.get(data1.url, data2 => {
        console.log(data2)
        $.get(data2.url, data3 => {
            console.log(data3)
        })
    })
})
複製代碼

代碼沒有美感,且不利於維護。固然,咱們能夠經過減小代碼嵌套,模塊化等手段來修復。可是並不以下面的解決方案優雅。 bash

佼佼者——promise

const reqMethod = (url) => {
    return new Promise((reslove, reject) => {
        $.get(url, data => {
            if(data.success) {
                reslove();
            } else {
                reject();
            }
        })
    })
}

reqMethod(url).then((data1) => {
    return reqMethod(data1.url);
}).then((data2) => {
    return reqMethod(data2.url);
}).then((data3) => {
    return reqMethod(data3.url);
})
複製代碼

這樣的實現方式,符合易於閱讀,由於每一步操做都是按照前後順序進行的。異步

promise是什麼

Promise從不一樣角度理解,有不少種含義。模塊化

promise的一種承諾

promise從字面意思理解,就是許諾和承諾的意思,對於一種承諾而言,有三種狀態:異步編程

一、承諾還未達成,還在糾結過程當中(pending狀態)
二、承諾沒有實現,失言了(rejected狀態)
三、承諾實現了,就是成功的狀態(fulfilled狀態)
複製代碼

Promise是一種標準的規範

在這裏不過多展開,你們能夠去看Promise/A+ 規範或者ECMAscript規範;函數

Promise是ES6提供的一種對象

Promise是一個對象,是一個構造函數,ES6將其寫進了語言標準。統一了用法。最基礎的用法以下:ui

const promiseMethod = new Promise((resolve, reject) => {
    // some code
    if(/*異步成功的條件*/) {
        resolve();
    } else {
        reject();
    }
})
複製代碼

Promise的用法

new Promise

Promise是一個構造函數,最基礎的做用就是用new操做符生成一個實例對象

const promiseMethod = new Promise((resolve, reject) => {
    // some code
    if(/*異步成功的條件*/) {
        resolve();
    } else {
        reject();
    }
})
複製代碼

Promise可接受的參數是一個函數,resolvereject是該函數的兩個參數,由js引擎提供,不須要本身定義。

resolve的做用是將pending狀態變動爲fulfilled狀態。

reject的做用是將pending狀態變動爲rejected狀態。

Promise新建時就會當即執行,與什麼時候調用無關,與結果也無關

舉個例子

const promiseOne = new Promise((resolve, reject) => {
    console.log('has finish');
    resolve();
})
複製代碼

一、console.log在新建過程當中就執行了。

二、promiseOne的結果已經固定下來了,不管什麼時候調用,結果都不會發生改變。

Promise.prototype.then

then方法是被定義在Promise的原型上,做用是:添加Promise狀態改變後的回調函數。

then方法接收兩個參數,第一個參數是resolve狀態執行的函數,第二個參數是reject執行的函數。

then方法返回的是一個新的Promise對象。

舉個例子:

const promiseOne = new Promise((resolve, reject) => {
    console.log('has finish');
    resolve();
})

const promiseTwo = new Promise((resolve, reject) => {
    console.log('has reject');
    reject();
})

const promiseThree = promiseOne.then(() => {
    console.log('成功的回調')
}, () => {
    console.log('失敗的回調')
})

promiseTwo.then(() => {
    console.log('成功的回調')
}, () => {
    console.log('失敗的回調')
})

console.log(promiseThree);

複製代碼

輸出的結果:

has finish
has reject
<Promise>(pending)
成功的回調
失敗的回調
複製代碼

Promise.prototype.catch

catch方法其實和then第二個回調函數的別名,做用是用於儲物發生時的回調處理。

catch捕獲兩類錯誤:

一、異步操做時拋出錯誤,致使狀態變爲reject

二、then回調過程當中產生的錯誤。

舉個例子:

//第一種狀況:異步操做過程當中報錯
const promise = new Promise(function(resolve, reject) {
  throw new Error('test');
});
promise.catch(function(error) {
  console.log(error);
});

// 第二種狀況:then執行過程當中報錯

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

promise.then(() => {
    throw new Error('test');
}).catch(function(error) {
  console.log(error);
});
複製代碼

關於catchthen第二個參數的區別:

// bad
promise
  .then(function(data) {
    // success
  }, function(err) {
    // error
  });

// good
promise
  .then(function(data) { //cb
    // success
  })
  .catch(function(err) {
    // error
  });
複製代碼

第二種寫法的優勢在於,then執行過程當中的報錯,catch同樣能捕獲,優於第一種寫法。

更深刻的錯誤捕獲咱們單獨放一章講。

Promise.prototype.finally

finally方法做用在於無論promise返回的狀態是什麼,它都會在執行。

finally不接受任何參數。

promise
  .then(function(data) {
    // success
  })
  .catch(function(err) {
    // error
  }).finally(() => {
      
  });
複製代碼

finally中執行的狀態與promise的結果無關,並且在方法中沒法得知promise的運行結果。

Promise.all

用法:

Promise.all([p1, p2, p3]);
複製代碼

做用:是將多個promise示例封裝成一個promise實例。結果只有如下兩種情形:

  • 所有成功

全部promise都成功,總的promise纔會成功

const p1 = new Promise((resolve, reject) => {
    resolve('hello');
})
const p2 = new Promise((resolve, reject) => {
    resolve('hello');
})
Promise.all([p1, p2]).then(() => {
    console.log('所有成功')
}).catch(() => {
    console.log('所有失敗')
})

// 所有成功
複製代碼
  • 所有失敗

只要有一個promise的狀態從pending變成reject就是失敗。

const p1 = new Promise((resolve, reject) => {
    resolve();
})
const p2 = new Promise((resolve, reject) => {
    reject();
})
Promise.all([p1, p2]).then(() => {
    console.log('所有成功')
}).catch(() => {
    console.log('所有失敗')
})
複製代碼

Promise的時間如何計算?

const newDate = new Date();
const p1 = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve();
    }, 500)
})
const p2 = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve();
    }, 1000)
})
Promise.all([p1, p2]).then(() => {
    const now = new Date();
    let time = now.getTime() - newDate.getTime();
    console.log(time);
})
// 1001
複製代碼

按最長的那個爲準。

Promise.race

用法:

Promise.race([p1, p2, p3]);
複製代碼

做用:是將多個promise示例封裝成一個promise實例。

Promise.race與Promise.all的區別

Promise.race的狀態取決於最早完成的promise的狀態。

舉個例子,咱們須要對一個請求作5秒的timeout,就能夠用Promise.race

const reqMethod = (url) => {
    return new Promise((reslove, reject) => {
        $.get(url, data => {
            reslove();
        })
    })
}

const timeout = new Promise((resolve, reject) => {
    setTimeout(() => {
        reject('timeout')
    }, 5000)
})
Promise.race([reqMethod(xxx),timeout]).then(() => {
    console.log('請求成功')
}).catch(() => {
    console.log('timeout')
})
複製代碼

Promise.resolve

做用:須要將現有的對象轉化爲promise對象。

Promise.resolve({});
//等價於
new Promise(resolve => resolve({}));
複製代碼

Promise.resolve會根據傳入的不一樣參數作不一樣的處理

  • 傳入一個promise對象

不作任何操做,原封不動的返回該對象。

  • 參數是一個thenable對象

當即執行thenable對象中的then方法,而後返回一個resolved狀態的promise

let thenable = {
    then: function(resolve, reject) {
        resolve('success')
    }
}

const p = Promise.resolve(thenable);
p.then((e) => {
    console.log(e);
})
複製代碼
  • 不是對象

直接返回一個resolved狀態的promise。並將參數帶給回調函數。

const p = Promise.resolve('參數');

p.then((e) => {
    console.log(e)
})
複製代碼
  • 不傳參數

直接返回一個resolved狀態的promise

Promise.reject

其做用是返回一個新的promise實例,狀態直接爲reject

Promise.reject('error');
//等價於
new Promise((resolve, reject) => reject('error'));
複製代碼

不一樣於Promise.resolve,其參數會原封不動的做爲reject的理由。

舉個例子:

const p = Promise.reject('error');
p.catch((e) => {
    console.log(e)
})
// error
複製代碼

Promise的優缺點

Promise的特色

  • 狀態不受外界影響,只知足於結果
一、promise表明一個異步操做,一共有三種狀態:pending、fulfilled和rejected。
二、promise的結果只服從於異步操做的結果,成功進入fulfilled狀態,失敗進入rejected狀態。
複製代碼
  • 狀態變動以後不會在改變
一、promise狀態變化只有兩種可能,一種從pending到fulfilled,或者是從pending到reject。
二、當狀態變動完時,狀態將不在發生改變。
複製代碼

Promise的優勢

  • 鏈式寫法可讀性比回調函數的寫法更優雅。
  • 與回調函數相比更加方便錯誤處理。
  • 與事件體系相比,一次性返回結果,更加適用於一次性返回的結果。

Promise的缺點

  • 不能取消執行過程。
  • 不能讀取異步過程當中的進度。
  • 生成以後對象結果不會改變。

結語

本文對promise的用法進行了詳解,以後會更新兩篇深刻一點的文章:

  • promise錯誤捕捉
  • 聊聊promise系列(深刻)
相關文章
相關標籤/搜索