咱們在寫前端代碼時,常常會對dom作事件處理操做,好比點擊、激活焦點、失去焦點等;再好比咱們用ajax請求數據,使用回調函數獲取返回值。這些都屬於異步編程。前端
也許你已經大概知道JavaScript引擎單線程的概念,那麼這種單線程模式和異步編程有什麼關係呢?vue
JavaScript引擎中,只有一個主線程,當執行JavaScript代碼塊時,不容許其餘代碼塊執行,而事件機制和回調機制的代碼塊會被添加到任務隊列(或者叫作堆棧)中,當符合某個觸發回調或者事件的時候,就會執行該事件或者回調函數。node
上面這段話的意思能夠這樣理解,假設你是一個修仙者,你去闖一個祕境,這個祕境就是主線程,你只能一直深刻下去,直到找到寶物和出口,而你還有一個自身的儲物空間,這個空間就相似堆棧,你在儲物空間放了不少你可能用到的法寶或者丹藥,這些東西就是回調函數和事件函數,當你遇到危險或者知足某個條件時,就能夠從儲物空間拿出你當前須要的東西。react
好吧,不扯這麼遠,下面看正題。ios
事件模型:
瀏覽器初次渲染DOM的時候,咱們會給一些DOM綁定事件函數,只有當觸發了這些DOM事件函數,纔會執行他們。ajax
const btn = document.querySelector('.button') btn.onclick = function(event) { console.log(event) }
回調模式:
nodejs中可能很是常見這種回調模式,可是對於前端來講,ajax的回調是最熟悉不過了。ajax回調有多個狀態,當響應成功和失敗都有不一樣的回調函數。編程
$.post('/router', function(data) { console.log(data) })
回調也可能帶來一個問題,那就是地獄回調,不過幸運的是,我從進入前端界開始,就使用react,跳過了不少坑,特別是地獄回調,一直沒有機會在工做中碰見到,真是遺憾。axios
事件函數沒有問題,咱們用的很爽,問題出在回調函數,尤爲是指地獄回調,Promise的出現正是爲了不地獄回調帶來的困擾。segmentfault
推薦你看JavaScript MDN Promise教程,而後再結合本文看,你就能學會使用Promise了。後端
Promise的中文意思是承諾,也就是說,JavaScript對你許下一個承諾,會在將來某個時刻兌現承諾。
react有生命週期,vue也有生命週期,就連Promise也有生命週期,如今生命週期咋這麼流行了。
Promise的生命週期:進行中(pending),已經完成(fulfilled),拒絕(rejected)
Promise被稱做異步結果的佔位符,它不能直接返回異步函數的執行結果,須要使用then(),當獲取異常回調的時候,使用catch()。
此次咱們使用axios插件的代碼作例子。axios是前端比較熱門的http請求插件之一。
一、建立axios實例instance。
import axios from 'axios' export const instance = axios.create()
二、使用axios實例 + Promise獲取返回值。
const promise = instance.get('url') promise.then(result => console.log(result)).catch(err => console.log(err))
Promise構造函數只有一個參數,該參數是一個函數,被稱做執行器,執行器有2個參數,分別是resolve()和reject(),一個表示成功的回調,一個表示失敗的回調。
new Promise(function(resolve, reject) { setTimeout(() => resolve(5), 0) }).then(v => console.log(v)) // 5
記住,Promise實例只能經過resolve或者reject函數來返回,而且使用then()或者catch()獲取,不能在new Promise裏面直接return,這樣是獲取不到Promise返回值的。
一、咱們也可使用Promise直接resolve(value)。
Promise.resolve(5).then(v => console.log(v)) // 5
二、也可使用reject(value)
Promise.reject(5).catch(v => console.log(v)) // 5
三、執行器錯誤經過catch捕捉。
new Promise(function(resolve, reject) { if(true) { throw new Error('error!!') } }).catch(v => console.log(v.message)) // error!!
不重要的內容,不用細看。
這裏涉及到nodejs環境和瀏覽器環境的全局,主要說的是若是執行了Promise.reject(),瀏覽器或者node環境並不會強制報錯,只有在你調用catch的時候,才能知道Promise被拒絕了。
這種行爲就像是,你寫了一個函數,函數內部有true和false兩種狀態,而咱們但願false的時候拋出錯誤,可是在Promise中,並不能直接拋出錯誤,不管Promise是成功仍是拒絕狀態,你獲取Promise生命週期的方法只能經過then()和catch()。
nodejs環境:
node環境下有個對象叫作process,即便你沒寫事後端node,若是寫過前端node服務器,也應該知道可使用process.ENV_NODE獲取環境變量。爲了監聽Promise的reject(拒絕)狀況,NodeJS提供了一個process.on(),相似jQuery的on方法,事件綁定函數。
process.on()有2個事件
unhandledRjection:在一個事件循環中,當Promise執行reject(),而且沒有提供catch()時被調用。
正常狀況下,你可使用catch捕捉reject。
Promise.reject("It was my wrong!").catch(v => console.log(v))
可是,有時候你不老是記得使用catch。你就須要使用process.on()
let rejected rejected = Promise.reject("It was my wrong!") process.on("unhandledRjection", function(reason, promise) { console.log(reason.message) // It was my wrong! console.log(rejected === promise) // true })
rejectionHandled:在一個事件循環後,當Promise執行reject,而且沒有提供catch()時被調用。
let rejected rejected = Promise.reject(new Error("It was my wrong!")) process.on("rejectionHandled", function(promise) { console.log(rejected === promise) // true })
異同:
事件循環中、事件循環後,你可能很難理解這2個的區別,可是這不重要,重要的是,若是你經過了catch()方法來捕捉reject操做,那麼,這2個事件就不會生效。
瀏覽器環境:
和node環境同樣,都提供了unhandledRjection、rejectionHandled事件,不一樣的是瀏覽器環境是經過window對象來定義事件函數。
let rejected rejected = Promise.reject(new Error("It was my wrong!")) window.rejectionHandled = function(event) { console.log(event) // true } rejectionHandled()
將代碼在瀏覽器控制檯執行一遍,你就會發現報錯了:Uncaught (in promise) Error: It was my wrong!
耶,你成功了!報錯內容正是你寫的reject()方法裏面的錯誤提示。
這個例子中,使用了3個then,第一個then返回 s * s,第二個then捕獲到上一個then的返回值,最後一個then直接輸出end。這就叫鏈式調用,很好理解的。我只使用了then(),實際開發中,你還應該加上catch()。
new Promise(function(resolve, reject) {
try {
resolve(5)
} catch (error) {
reject('It was my wrong!!!')
}
}).then(s => s * s).then(s2 => console.log(s2)).then(() => console.log('end'))
// 25 "end"
在Promise的構造函數中,除了reject()和resolve()以外,還有2個方法,Promise.all()、Promise.race()。
Promise.all():
前面咱們的例子都是隻有一個Promise,如今咱們使用all()方法包裝多個Promise實例。
語法很簡單:參數只有一個,可迭代對象,能夠是數組,或者Symbol類型等。
Promise.all(iterable).then().catch()
示例:傳入3個Promise實例
Promise.all([ new Promise(function(resolve, reject) { resolve(1) }), new Promise(function(resolve, reject) { resolve(2) }), new Promise(function(resolve, reject) { resolve(3) }) ]).then(arr => { console.log(arr) // [1, 2, 3] })
Promise.race():語法和all()同樣,可是返回值有所不一樣,race根據傳入的多個Promise實例,只要有一個實例resolve或者reject,就只返回該結果,其餘實例再也不執行。
仍是使用上面的例子,只是我給每一個resolve加了一個定時器,最終結果返回的是3,由於第三個Promise最快執行。
Promise.race([ new Promise(function(resolve, reject) { setTimeout(() => resolve(1), 1000) }), new Promise(function(resolve, reject) { setTimeout(() => resolve(2), 100) }), new Promise(function(resolve, reject) { setTimeout(() => resolve(3), 10) }) ]).then(value => { console.log(value) // 3 })
派生的意思是定義一個新的Promise對象,繼承Promise方法和屬性。
class MyPromise extends Promise { //從新封裝then() success(resolve, reject) { return this.then(resolve, reject) } //從新封裝catch() failer(reject) { return this.catch(reject) } }
接着咱們來使用一下這個派生類。
new MyPromise(function(resolve, reject) { resolve(10) }).success(v => console.log(v)) // 10
若是隻是派生出來和then、catch同樣的方法,我想,你不會幹這麼無聊的事情。
Promise自己不是異步的,只有他的then()或者catch()方法纔是異步,也能夠說Promise的返回值是異步的。一般Promise被使用在node,或者是前端的ajax請求、前端DOM渲染順序等地方。
在本章你只須要了解有async這個將來的方案,推薦不會的趕忙去網上找資料學,反正我是已經在實際項目中全面開展async了。
async function a() { await function() {}} }
Promise是什麼、怎麼用、怎麼獲取返回值?是本章的中心內容,多看幾遍,你會發現使用Promise是很是簡單的事情。