一篇文章帶你瞭解和使用Promise物件
Promise的理解與使用
什麼是Promise
promise是解決非同步程式設計的一種方案,對比傳統的回撥函式更加的便捷和強大。
Promise的特點
- promise物件的狀態不受外界影響,共有三種狀態:pending(進行中)、fulfilled(已成功)、rejected(已失敗),且同一時間只能存在一種狀態
- 一旦狀態改變就不會被改變,且任何時候都可以得到這個結果;Promise物件的狀態改變,只有兩種可能:從pending變為fulfilled(後面簡稱resolved狀態)和從pending變為rejected(後面簡稱rejected狀態)。只要這兩種情況發生,狀態就凝固了,不會再變了。
Promise的基礎用法
Promise例項化
Promise實際是一個建構函式,用於生產promise例項
// promise一旦例項化就會立即執行
let promise = new Promise((resolve, reject) => {
// 非同步函式執行成功時執行回撥,若resolve函式有帶引數,則該引數會傳遞給回撥函式
resolve()
// 非同步函式執行失敗時執行回撥,若reject函式有帶引數,則該引數會傳遞給回撥函式
reject()
})
// 如果你希望這個promise是可控的
function controllPromise () {
return new Promise(...)
}
示例
現在有一個非同步函式獲取資料,當該非同步函式執行完成後列印結果
let fn1 = function () {
return new Promise((resolve, reject) => {
// 定時器模擬非同步獲取資料
setTimeout(() => {
// 將獲取的結果傳入resolve函式中
resolve('fn1')
}, 1000)
})
}
// promise狀態改變後會呼叫then方法繫結的函式,且該函式接收一個引數,該引數預設是undefined,或者是promise函式resolve、reject的值
fn1().then((res) => {
console.log(res)
})
// 最終列印結果:fn1
resolve函式傳入的是promise
還有另外一種情況,若resolve或者reject函式傳入的引數是promise物件,那麼舊的promise函式物件的狀態由新的promise物件的狀態來決定,簡單的說就是,若resolve或者reject函式傳入的引數是promise物件需要等待該promise物件執行完成才會執行回撥。
let fn1 = function () {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve('fn1')
}, 1000)
})
}
let fn2 = function () {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(fn1())
}, 1000)
})
}
fn2().then((res) => {
console.log(res)
})
// 2s 後列印:fn1
Promise.prototype.then()
then方法是在Promise的原型上的,也就是說,then方法可以被所有Promise例項呼叫。then方法的第一個引數是resolved狀態的的回撥函式,第二個引數是rejected狀態的回撥函式(可選),而且then方法返回的是一個新的promise例項(不是原來的那個),所以then後面還可以繼續呼叫then方法等其他promise例項上的方法
示例
let fn1 = function () {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve('fn1')
}, 1000)
})
}
fn1().then((res) => {
console.log(res)
}).then((res) => {
console.log(res)
})
// 列印結果:fn1 undefined
then方法回撥函式的引數與返回值
我們從上面的示例可以知道,then方法回撥函式會接收一個變數res(自定義),這個變數實際就是前一個promise例項中resolve方法傳入的值,因此會列印 fn1,那麼為何後面會列印一個undefined呢,那是因為我們 then 方法沒有給返回值,第二個then方法的回撥函式接收的引數是上一個then方法回撥函式的返回值,我們來看示例
示例
let fn1 = function () {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve('fn1')
}, 1000)
})
}
fn1().then((res) => {
// res 會接收上一個promise示例的resolve值
console.log(res)
return '我是第二個then方法回撥函式的引數'
}).then((res) => {
// res 會接收上一個then方法的返回值
console.log(res)
})
// 列印結果:fn1 我是第二個then方法回撥函式的引數
同樣的,如果回撥函式返回值是一個promise,那麼他會等待該promise物件執行完成在去執行下一個then方法的回撥函式
示例
let fn1 = function () {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve('fn1')
}, 1000)
})
}
let fn2 = function () {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve('fn2')
}, 1000)
})
}
fn1().then((res) => {
console.log(res)
return fn2()
}).then((res) => {
console.log(res)
})
// 列印結果:fn1(1s後) fn2(1s後)
Promise.prototype.catch()
Promise.prototype.catch()方法是.then(null, rejection)或.then(undefined, rejection)的別名,用於指定發生錯誤時的回撥函式。如果執行中丟擲錯誤,也會被catch()方法捕獲
示例
let fn1 = function () {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve('fn1')
}, 1000)
})
}
let fn2 = function () {
return new Promise((resolve, reject) => {
setTimeout(() => {
reject('error test')
}, 1000)
})
}
// 接收reject丟擲的錯誤
fn1().then((res) => {
console.log(res)
return fn2()
}).then((res) => {
console.log(res)
}).catch((err) => {
console.log('error:' + err)
})
// 列印結果:
// fn1
// error:error test
// 接收常規執行丟擲的錯誤,如throw
fn1().then((res) => {
console.log(res)
throw('error normal')
}).then((res) => {
console.log(res)
}).catch((err) => {
console.log('error:' + err)
})
// 列印結果:
// fn1
// error:error normal
Promise 在resolve語句後面,再丟擲錯誤,不會被捕獲,等於沒有丟擲。因為 Promise 的狀態一旦改變,就永久保持該狀態,不會再變了。
示例
let fn1 = function () {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve('fn1')
throw 'error'
}, 1000)
})
}
fn1().then((res) => {
console.log(res)
}).catch((err) => {
console.log(err)
})
// 列印結果
// fn1
Promise 物件的錯誤具有“冒泡”性質,會一直向後傳遞,直到被捕獲為止。也就是說,錯誤總是會被下一個catch語句捕獲。
示例
// 假設fn1, fn2, fn3是三個獲取資料的非同步函式
fn1().then(() => {
return fn2()
}).then(() => {
return fn3()
}).catch((err) => {
console.log(err)
})
// 若fn1, fn2, fn3其中一個非同步函式丟擲錯誤都會被catch語句捕獲
跟傳統的try/catch程式碼塊不同的是,如果沒有使用catch()方法指定錯誤處理的回撥函式,Promise 物件丟擲的錯誤不會傳遞到外層程式碼,這就是說,Promise 內部的錯誤不會影響到 Promise 外部的程式碼,通俗的說法就是“Promise 會吃掉錯誤”。
const someAsyncThing = function() {
return new Promise(function(resolve, reject) {
// 下面一行會報錯,因為x沒有宣告
resolve(x + 2);
});
};
someAsyncThing().then(function() {
console.log('everything is great');
});
setTimeout(() => { console.log(123) }, 2000);
// Uncaught (in promise) ReferenceError: x is not defined
// 123
在鏈式呼叫中可以存在多個catch,但一般來說會執行第一個catch到的錯誤,catch只會捕獲到前面丟擲的錯誤,無法捕獲到後面函式丟擲的錯誤,所以建議將catch放在鏈式呼叫的最後一步
Promise.all()
Promise.all()方法用於將多個promise例項,包裝組合成新的Promise例項,Promise.all()方法接受一個陣列作為引數,p1、p2、p3都是 Promise 例項,如果不是,就會先呼叫Promise.resolve方法,將引數轉為 Promise 例項,再進一步處理。另外,Promise.all()方法的引數可以不是陣列,但必須具有 Iterator 介面,且返回的每個成員都是 Promise 例項。
const all = Promise.all([p1,p2,p3])
p的狀態由p1、p2、p3決定,分成兩種情況。
(1)只有p1、p2、p3的狀態都變成fulfilled,all的狀態才會變成fulfilled,此時p1、p2、p3的返回值組成一個陣列,傳遞給p的回撥函式。
(2)只要p1、p2、p3之中有一個被rejected,all的狀態就變成rejected,此時第一個被reject的例項的返回值,會傳遞給p的回撥函式。
簡單來說就是,當p1、p2、p3全部成功的時候執行回撥,並返回陣列,若有失敗返回值則優先返回最先丟擲錯誤那個
示例
function p1() {
return 1
}
function p2() {
return new Promise((res, rej) => {
setTimeout(() => {
res('2')
}, 4000)
})
}
function p3() {
return new Promise((res, rej) => {
setTimeout(() => {
res('3')
}, 1000)
})
}
function test_err() {
return new Promise((res, rej) => {
setTimeout(() => {
rej('error')
}, 3000)
})
}
// 全部成功,返回一個陣列,陣列包含了返回的結果,且順序同傳入promise時的例項順序相同
Promise.all([
p1(),
p2(),
p3()
]).then((res) => {
console.log(res)
})
// 列印結果
// [1, "2", "3"]
Promise.all([
p1(),
p2(),
p3(),
test_err()
]).catch((res) => {
console.log(res)
})
// 列印結果
// error
Promise.race
Promise.race()方法同樣是將多個 Promise 例項,包裝成一個新的 Promise 例項。
const race = Promise.race([p1, p2, p3])
但和Promise.all()方法不同的是,當p1, p2, p3中有一個率先改變狀態,那麼race的狀態就會跟著改變,那個率先改變的Promise示例的返回值就會傳遞給race的回撥函式
我們用一個超時機制的例子來說明一下
// 假設fn1是獲取資料的函式,獲取資料所用時間未知當獲取時間超過3s我們就判斷它超時了
function fn1() {
...
}
function overtime() {
return new Promise((res ,rej) => {
setTimeout(() => {
res('overtime')
}, 3000)
})
}
const race = new Promise.race([fn1(), overtime()])
// 若超過3秒,fn1還未獲取到資料,就會返回overtime
Promise.resolve()
Promise.resolve()方法可以將一個現有的變數轉換為Promise物件
const jsp = Promise.resolve('1')
// 等價於
function jsp = new Promise((res ,rej) => {
res('1')
})
resolve方法接收的引數可以分為4中情況
-
引數是一個Promise例項
如果引數是 Promise 例項,那麼Promise.resolve將不做任何修改、原封不動地返回這個例項。
-
引數是一個thenable物件
thenable物件指的是具有then方法的物件,比如下面這個物件。
let thenable = { then: function(resolve, reject) { resolve(42); }
Promise.resolve()方法會將這個物件轉為 Promise 物件,然後就立即執行thenable物件的then()方法。
-
引數不是具有then()方法的物件,或根本就不是物件
如果引數是一個原始值,或者是一個不具有then()方法的物件,則Promise.resolve()方法返回一個新的 Promise 物件,狀態為resolved。
-
不帶有任何引數
Promise.resolve()方法允許呼叫時不帶引數,直接返回一個resolved狀態的 Promise 物件。
如果希望得到一個 Promise 物件,比較方便的方法就是直接呼叫Promise.resolve()方法。
Promise.reject()
Promise.reject(reason)方法也會返回一個新的 Promise 例項,該例項的狀態為rejected。
const p = Promise.reject('出錯了');
// 等同於
const p = new Promise((resolve, reject) => reject('出錯了'))
p.then(null, function (s) {
console.log(s)
});
// 出錯了
上面程式碼生成一個 Promise 物件的例項p,狀態為rejected,回撥函式會立即執行。
Promise.reject()方法的引數,會原封不動地作為reject的理由,變成後續方法的引數。
Promise.reject('出錯了')
.catch(e => {
console.log(e === '出錯了')
})
// true
上面程式碼中,Promise.reject()方法的引數是一個字串,後面catch()方法的引數e就是這個字串。
本文借鑑的阮一峰 promise入門,這篇文章講的很詳細,有興趣可以看看
相關文章
- 一篇文章帶你初步瞭解—CSS特指度CSS
- 一篇文章帶你瞭解HTML5 MathMLHTML
- 一篇文章帶你瞭解——Kotlin協程Kotlin
- 一篇文章帶你瞭解介面自動化
- 一篇文章帶你瞭解高質量代理ip的使用技巧
- 一篇文章帶你瞭解HTML格式化元素HTML
- 一篇文章帶你瞭解CSS 分頁例項CSS
- 一篇文章帶你瞭解高可用架構分析架構
- 一篇文章帶你瞭解設計模式——建立者模式設計模式
- 一篇文章帶你瞭解設計模式——結構型模式設計模式
- 一篇文章帶你瞭解如何測試訊息佇列佇列
- 你真的瞭解python嗎?這篇文章帶你快速瞭解!Python
- 一篇文章帶你瞭解Python基礎測試工具——UnitTestPython
- 機器學習到底是什麼?一篇文章帶你瞭解透徹機器學習
- 一篇帶你瞭解TCP/IP 概念TCP
- 什麼是工藝流程圖?一篇文章帶你詳細瞭解流程圖
- 一篇文章帶你更深入瞭解區塊鏈有哪些應用?區塊鏈
- 一篇文章幫你瞭解 PHP 7.3 更新PHP
- 一篇文章帶你瞭解設計模式原理——UML圖和軟體設計原則設計模式
- 一篇文章帶你瞭解網路爬蟲的概念及其工作原理爬蟲
- 什麼是Python爬蟲?一篇文章帶你全面瞭解爬蟲Python爬蟲
- 一文章帶你瞭解微服務微服務
- 一篇文章帶你瞭解Python常用自動化測試框架——PytestPython框架
- (圖解 HTTP)一篇文章帶你深入理解 IP、TCP 和 DNS圖解HTTPTCPDNS
- 一篇文章瞭解大前端前端
- 一篇文章帶你吃透 Docker 原理Docker
- 一篇文章帶你入門Zookeeper
- 一篇文章讓你徹底瞭解Java內部類Java
- 一篇文章帶你瞭解 Java 自動記憶體管理機制及效能優化Java記憶體優化
- 一篇文章瞭解JsBridgeJS
- 帶你瞭解webpackWeb
- 一篇文章瞭解什麼是AQS和應用AQS
- 帶你真正的瞭解加密和Hash加密
- 一篇文章帶你認識 SpringSecuritySpringGse
- 這一篇就夠啦,帶你瞭解MySQL的常用技巧MySql
- 一篇文章瞭解Redis資料庫Redis資料庫
- 一篇文章瞭解RPC框架原理RPC框架
- 分享一篇文章 "深入瞭解 Go 方法"Go