JavaScript 解決異步編程有兩種主要方式:事件模型和回調函數。可是隨着業務愈來愈複雜,這兩種方式已經不能知足開發者的需求了,Promise 能夠解決這方面的問題。爲了更好的理解 Promise 是如何工做的,咱們先來了解一下傳統的異步編程的方式。javascript
let button = document.getElementId("my-btn"); button.onclick = function() { console.log("Hello"); }
任務的執行不取決於代碼的順序,而取決於某個事件是否發生。上面代碼中,console.log("Hello") 直到 button 被點擊後纔會被執行。當 button 被點擊,賦值給 onclick 的函數就被添加到做業隊列的尾部,並在隊列前部全部任務結束以後再執行。java
事件模型適用於處理簡單的交互,若將多個獨立的異步調用鏈接在一塊兒,必須跟蹤每一個事件的事件目標,會使程序更加複雜,運行流程會變得很不清晰。jquery
咱們來看一下比較經典的使用 jsonp 解決跨域問題的示例:ios
function callback (res) { document.getElementById('d1').innerHTML = res.result.address console.log('Your public IP address is: ', res) } function jsonp (lat, lng) { let src = `https://apis.map.qq.com/ws/geocoder/v1/?location=${lat},${lng}&key=yourKey&output=jsonp&callback=callback` let script = document.createElement('script') script.setAttribute("type","text/javascript") script.src = src; document.body.appendChild(script) } jsonp(39.92, 116.43)
初看這種模式運做得至關好,簡單、容易理解,但你可能會迅速的發現這樣的模式不利於代碼的閱讀和維護,各個部分之間耦合度過高,容易陷入回調地獄。就像這樣:es6
method1(function(err, result) { if (err) { throw err } method2(function(err, result) { if (err) { throw err } method3(function(err, result) { if (err) { throw err } method4(function(err, result) { if (err) { throw err } method5(result) }) }) }) })
Promise 能大幅度改善這種狀況。咱們來看下Promise 是如何實現的:web
let promise = new Promise((resolve, reject) => { // ... method 1 some code if (/* 異步操做成功 */){ resolve(value); } else { reject(error); } }) promise.then((value) => { // method 2 some code }).then((value) => { // method 3 some code }).then((value) => { // method 4 some code }).then((value) => { // method 5 some code }).catch((err) => { // some err code })
是否是清晰不少?是否是很神奇?接下來一塊兒來學習一下 Promise 吧!npm
咱們先來看下 Promise 的方法有哪些:編程
Promise.Prototype.then()
Promise.Prototype.catch()
Promise.Prototype.finally()
Promise.all()
Promise.race()
Promise.resolve()
Promise.reject()
Promise.try()
Promise 函數的執行,都是依賴於狀態的改變,這三種狀態要記牢哦:json
Pending:進行中
Fulfilled:已成功
Rejected:已失敗
Promise 優勢:axios
1)對象的狀態不受外界影響。
2)一旦狀態改變,就不會再變,任什麼時候候均可以獲得這個結果。
3)將異步操做以同步操做的流程表達出來,避免了層層嵌套回調函數。
4)提供統一的接口,使得控制異步操做更加容易。
Promise 缺點:
1)沒法取消 Promise,一旦新建它就會當即執行,沒法中途取消。
2)若是不設置回調函數,Promise 內部拋出的錯誤,不會反映到外部。
3)當處於 pending 狀態時,沒法得知目前進展到哪個階段(剛剛開始仍是即將完成)。
瞭解了 Promise 的方法,3種狀態以及特色和優缺點以後,接下來咱們來看一下 Promise 是怎麼使用的。
咱們來創造一個讀取文件的 Promise 實例:
const fs = require('fs') const path = require('path') function readFile (filename) { return new Promise (function (resolve, reject) { // reject(new Error('err')) //觸發異步操做 fs.readFile(filename, {encoding: 'utf8'}, function (err, contents) { // 檢查錯誤 if (err) { reject(err) return } //讀取成功 resolve(contents) }) }) } let promise = readFile(path.resolve(__dirname, '../json/a.json'))
上述實例中 resolve 函數的做用是,將 Promise 對象的狀態從「未完成」變爲「成功」(即從 pending 變爲 resolved),在異步操做成功時調用,並將異步操做的結果,做爲參數傳遞出去;
reject 函數的做用是,將 Promise 對象的狀態從「未完成」變爲「失敗」(即從 pending 變爲 rejected),在異步操做失敗時調用,並將異步操做報出的錯誤,做爲參數傳遞出去。
Promise 實例生成之後,就能夠用 then 方法來分別指定 resolved 狀態和 rejected 狀態的回調函數了。
promise.then(function (contents) { // 完成 console.log(contents) return(contents) }, function (err) { // 失敗 console.log(err.message) })
咱們能夠看到,then 方法接受兩個回調函數做爲參數;
第一個回調函數在 Promise 對象的狀態變爲 resolved 時調用;
第二個回調函數在 Promise 對象的狀態變爲 rejected 時調用;
其中,第二個函數是可選的。這兩個函數都接受 Promise 對象傳出的值做爲參數。
Promise.then 方法每次調用,都返回一個新的 Promise 對象,因此支持鏈式寫法。
let taskA = (value) => { console.log("Task A") console.log(value) return value } let taskB = (value) => { console.log("Task B") console.log(value) } promise .then(taskA) .then(taskB) .catch((err) => { console.log(err.message) })
Promise.prototype.catch 方法是 .then(null, rejection) 的別名,至關於 then 函數的第一個參數傳入 null,第二個參數傳入發生錯誤時的回調函數。
promise.then(function(value) { // 成功 console.log(value) }).catch(function (err) { // 失敗 console.log(err.message) })
finally 方法用於指定無論 Promise 對象最後狀態如何,都會執行的操做。該方法是 ES2018 引入標準的,目前大部分瀏覽器還不支持,不過能夠本身實現。
finally 方法的實現:
Promise.prototype.finally = function (callback) { let P = this.constructor return this.then( value => P.resolve(callback()).then(() => value), reason => P.resolve(callback()).then(() => { throw reason }) ) }
finally 方法的使用:
promise .then((contents) => { console.log(contents) return contents }) .catch((err) => { console.log(err.message) }) .finally(() => { console.log('finally') })
Promise.all 方法能夠將多個 Promise 實例,包裝成一個新的 Promise 實例。
const p = Promise.all([p1, p2, p3]);
新的 Promise p 的狀態由 p一、p二、p3 決定,只有當 p一、p二、p3 的狀態都變成了 fulfilled,p 的狀態纔會變成 fulfilled;只要 p一、p二、p3 之中有一個被 rejected,p 的狀態就變成了 rejected。
注意,若是做爲參數的 Promise 實例,本身定義了 catch 方法,那麼它一旦被 rejected,並不會觸發Promise.all()的 catch 方法的。
若是 p2 有本身的 catch 方法,就不會調用 Promise.all() 的 catch 方法。
const p1 = new Promise((resolve, reject) => { resolve('hello'); }) .then(result => result) .catch(e => e); const p2 = new Promise((resolve, reject) => { throw new Error('報錯了'); }) .then(result => result) .catch(e => e); Promise.all([p1, p2]) .then(result => console.log(result)) .catch(e => console.log(e)); // ["hello", Error: 報錯了]
若是 p2 沒有本身的 catch 方法,就會調用 Promise.all() 的 catch 方法。
const p1 = new Promise((resolve, reject) => { resolve('hello'); }) .then(result => result); const p2 = new Promise((resolve, reject) => { throw new Error('報錯了'); }) .then(result => result); Promise.all([p1, p2]) .then(result => console.log(result)) .catch(e => console.log(e)); // Error: 報錯了
Promise.race 方法一樣是將多個 Promise 實例,包裝成一個新的 Promise 實例。
const p = Promise.race([p1, p2, p3]);
新的Promise p,只要 p一、p二、p3 之中有一個實例率先改變狀態,p 的狀態就跟着改變。那個率先改變的 Promise 實例的返回值,就傳遞給 p 的回調函數。
function timerPromisefy(delay) { return new Promise(function (resolve, reject) { setTimeout(function () { resolve(delay) }, delay) }) } Promise.race([ timerPromisefy(10), timerPromisefy(20), timerPromisefy(30) ]).then(function (values) { console.log(values) // 10 })
有時須要將現有對象轉爲 Promise 對象,Promise.resolve 方法就起到這個做用,返回一個 fulfilled 狀態的 Promise 對象。
const promise = Promise.resolve('hello'); promise.then(function(value){ console.log(value); }); // 至關於 const promise = new Promise(resolve => { resolve('hello'); }); promise.then((value) => { console.log(value) })
Promise.reject(reason) 方法也會返回一個新的 Promise 實例,該實例的狀態爲 rejected。
const p = Promise.reject('出錯了'); p.then(null, (value) => { console.log(value) }) // 等同於 const p = new Promise((resolve, reject) => reject('出錯了')) p.then(null, (value) => { console.log(value) })
讓同步函數同步執行,異步函數異步執行。
const f = () => console.log('now'); Promise.try(f); console.log('next'); // now // next
實現方法:
function loadImageAsync(url) { return new Promise(function(resolve, reject) { const image = new Image() image.onload = function() { resolve(image) } image.onerror = function() { reject(new Error('Could not load image at ' + url)) } image.src = url }) }
調用:
loadImageAsync('圖片路徑').then((value) => { document.getElementById('d1').appendChild(value) }).catch((err) => { console.log(err.message) })
實現方法:
let loadScript = function () { return function _loadScript(url, callBack) { return new Promise(function (resolve) { let script = document.createElement('script') script.type = 'text/javascript' if (script.readyState) { // 兼容IE的script加載事件 script.onreadystatechange = function () { // loaded : 下載完畢 complete: 數據準備完畢。這兩個狀態ie可能同時出現或者只出現一個 if (script.readyState === 'loaded' || script.readyState === 'complete') { // 防止加載兩次 script.onreadystatechange = null callBack() // 把函數傳遞下去,保證能順序加載js resolve(_loadScript) } } } else { script.onload = function () { callBack() resolve(_loadScript) } } script.src = url document.head.appendChild(script) }) } }()
調用:
loadScript('http://code.jquery.com/jquery-3.2.1.min.js ', () => {}) .then(() => { $("#d1").on('click', () => {alert(1)}) }).catch((err) => { console.log(err) })
import axios from './axios' import qs from 'qs' const config = { time: +new Date() + '', timeout: 6000, headers: { 'Content-Type': 'application/x-www-form-urlencoded', time: new Date().getTime() } } function checkResponse (response, notice) { return new Promise((resolve, reject) => { let code = Number(response.code) if (code === 0 || code === 200 || code === 2000 || code === 1 || code === 2 || code === '0' || code === 109) { resolve(response) } else { if (notice) { // 提示信息 console.log('response-notice', notice) } reject(response) } }) } function fixURL (url, type) { let result = '' switch (type) { case 'r': result += `/api/v2${url}` break } return result } /** * Requests a URL, returning a promise. * * @param {object} [options] The options we want to pass to axios * @param {string} [options.url] 請求的url地址(必須) * @param {string} [options.method] 請求方式, get or post,默認post * @param {object} [options.data] 請求參數 * @param {number} [options.timeout] 請求超時時間 * @param {boolean} [options.notice] 請求失敗是否顯示提示,默認true * @return {object} promise對象 */ function request (options = {}) { let { url, method, data, timeout, headers, type, notice } = options method = method || 'post' data = data || {} type = type || 't' timeout = timeout || config.timeout headers = Object.assign({}, config.headers, headers) notice = notice === undefined ? true : notice let result = {} if (method === 'get') { result = new Promise((resolve, reject) => { axios({ method: 'get', url: fixURL(url, type), params: data, timeout, headers }) .then((res) => { checkResponse(res.data, notice).then((data) => { resolve(data) }) .catch((data) => { reject(data) }) }) .catch((data) => { reject(data) }) }) } else if (method === 'post') { result = new Promise((resolve, reject) => { axios({ method: 'post', url: fixURL(url, type), data: headers['Content-Type'] === 'application/x-www-form-urlencoded' ? qs.stringify(data) : data, timeout, headers }) .then((res) => { checkResponse(res.data, notice).then((data) => { resolve(data) }) .catch((data) => { reject(data) }) }) .catch((data) => { reject(data) }) }) } return result } export default request
// 判斷變量否爲function const isFunction = variable => typeof variable === 'function' // 定義Promise的三種狀態常量 const PENDING = 'PENDING' const FULFILLED = 'FULFILLED' const REJECTED = 'REJECTED' class MyPromise { constructor (handle) { if (!isFunction(handle)) { throw new Error('MyPromise must accept a function as a parameter') } // 添加狀態 this._status = PENDING // 添加狀態 this._value = undefined // 添加成功回調函數隊列 this._fulfilledQueues = [] // 添加失敗回調函數隊列 this._rejectedQueues = [] // 執行handle try { handle(this._resolve.bind(this), this._reject.bind(this)) } catch (err) { this._reject(err) } } // 添加resovle時執行的函數 _resolve (val) { const run = () => { if (this._status !== PENDING) return // 依次執行成功隊列中的函數,並清空隊列 const runFulfilled = (value) => { let cb; while (cb = this._fulfilledQueues.shift()) { cb(value) } } // 依次執行失敗隊列中的函數,並清空隊列 const runRejected = (error) => { let cb; while (cb = this._rejectedQueues.shift()) { cb(error) } } /* 若是resolve的參數爲Promise對象,則必須等待該Promise對象狀態改變後, 當前Promsie的狀態纔會改變,且狀態取決於參數Promsie對象的狀態 */ if (val instanceof MyPromise) { val.then(value => { this._value = value this._status = FULFILLED runFulfilled(value) }, err => { this._value = err this._status = REJECTED runRejected(err) }) } else { this._value = val this._status = FULFILLED runFulfilled(val) } } // 爲了支持同步的Promise,這裏採用異步調用 setTimeout(run, 0) } // 添加reject時執行的函數 _reject (err) { if (this._status !== PENDING) return // 依次執行失敗隊列中的函數,並清空隊列 const run = () => { this._status = REJECTED this._value = err let cb; while (cb = this._rejectedQueues.shift()) { cb(err) } } // 爲了支持同步的Promise,這裏採用異步調用 setTimeout(run, 0) } // 添加then方法 then (onFulfilled, onRejected) { const { _value, _status } = this // 返回一個新的Promise對象 return new MyPromise((onFulfilledNext, onRejectedNext) => { // 封裝一個成功時執行的函數 let fulfilled = value => { try { if (!isFunction(onFulfilled)) { onFulfilledNext(value) } else { let res = onFulfilled(value); if (res instanceof MyPromise) { // 若是當前回調函數返回MyPromise對象,必須等待其狀態改變後在執行下一個回調 res.then(onFulfilledNext, onRejectedNext) } else { //不然會將返回結果直接做爲參數,傳入下一個then的回調函數,並當即執行下一個then的回調函數 onFulfilledNext(res) } } } catch (err) { // 若是函數執行出錯,新的Promise對象的狀態爲失敗 onRejectedNext(err) } } // 封裝一個失敗時執行的函數 let rejected = error => { try { if (!isFunction(onRejected)) { onRejectedNext(error) } else { let res = onRejected(error); if (res instanceof MyPromise) { // 若是當前回調函數返回MyPromise對象,必須等待其狀態改變後在執行下一個回調 res.then(onFulfilledNext, onRejectedNext) } else { //不然會將返回結果直接做爲參數,傳入下一個then的回調函數,並當即執行下一個then的回調函數 onFulfilledNext(res) } } } catch (err) { // 若是函數執行出錯,新的Promise對象的狀態爲失敗 onRejectedNext(err) } } switch (_status) { // 當狀態爲pending時,將then方法回調函數加入執行隊列等待執行 case PENDING: this._fulfilledQueues.push(fulfilled) this._rejectedQueues.push(rejected) break // 當狀態已經改變時,當即執行對應的回調函數 case FULFILLED: fulfilled(_value) break case REJECTED: rejected(_value) break } }) } // 添加catch方法 catch (onRejected) { return this.then(undefined, onRejected) } // 添加靜態resolve方法 static resolve (value) { // 若是參數是MyPromise實例,直接返回這個實例 if (value instanceof MyPromise) return value return new MyPromise(resolve => resolve(value)) } // 添加靜態reject方法 static reject (value) { return new MyPromise((resolve ,reject) => reject(value)) } // 添加靜態all方法 static all (list) { return new MyPromise((resolve, reject) => { /** * 返回值的集合 */ let values = [] let count = 0 for (let [i, p] of list.entries()) { // 數組參數若是不是MyPromise實例,先調用MyPromise.resolve this.resolve(p).then(res => { values[i] = res count++ // 全部狀態都變成fulfilled時返回的MyPromise狀態就變成fulfilled if (count === list.length) resolve(values) }, err => { // 有一個被rejected時返回的MyPromise狀態就變成rejected reject(err) }) } }) } // 添加靜態race方法 static race (list) { return new MyPromise((resolve, reject) => { for (let p of list) { // 只要有一個實例率先改變狀態,新的MyPromise的狀態就跟着改變 this.resolve(p).then(res => { resolve(res) }, err => { reject(err) }) } }) } finally (cb) { return this.then( value => MyPromise.resolve(cb()).then(() => value), reason => MyPromise.resolve(cb()).then(() => { throw reason }) ); } }
參考文章
Promise 官網
ECMAScript 6 入門
Promise 源碼詳解
Promise 實現原理(附源碼)
es6-promise-try npm
JavaScript Promise:簡介