一、概述
ES6的Promise對象可解決前端開發中常見的回調地獄問題(即大量異步回調函數的嵌套) 爲異步編程提供了一種很好的解決方案
首先 什麼是異步問題
例:
一個異步的讀取文件的方法
const fs=require("fs")
const path=require("path")
function getFileByPath(filePath)
{
fs.readFile(filePath,"utf-8",(err,dataStr) => {
if (err)
{
throw err;
}
return dataStr;
})
}
var result=getFileByPath(path.join(__dirname,"./files/1.txt"))
console.log(result)
此時 若執行代碼 輸出的是undefined 而並不是從文件中讀取的內容
這是因爲 fs.readFile是個異步的讀文件方法 主程序不會執行它 而是放入隊列中 讓子程序執行
主程序將其放入隊列中之後就不管它了 然後到了方法結尾 發現沒有return 於是 返回了undefined
這樣就拿不到返回的值
可用回調函數解決此問題:
傳入一個函數 在傳入的函數中獲取到異步方法裏的值進行處理
function getFileByPath(filePath,callback)
{
fs.readFile(filePath,"utf-8",(err,dataStr) => {
if (err)
{
throw err;
}
callback(dataStr)
})
}
getFileByPath(path.join(__dirname,"./files/1.txt"),function(dataStr){
console.log(dataStr)
})
兩個回調函數:一個處理成功後的回調 一個處理髮生異常時的回調
const fs=require("fs")
const path=require("path")
function getFileByPath(filePath,successCallback,errorCallback)
{
fs.readFile(filePath,"utf-8",(err,dataStr) => {
if (err)
{
return errorCallback(err);
}
successCallback(dataStr)
})
}
getFileByPath(path.join(__dirname,"./files/1.txt"),function(data){
console.log(data);
},
function(err){
console.log(err.message);
}
)
當有個需求:先讀取文件1 再讀取文件2 最後讀取文件3 並按此順序輸出讀取的內容
此時 若三個函數調用的時候直接是同級並排的 由於是異步函數 因此輸出時會不按規定的順序
若要按指定順序輸出 只能一個個的嵌套:
getFileByPath(path.join(__dirname,"./files/1.txt"),function(data){
console.log(data);
getFileByPath(path.join(__dirname,"./files/2.txt"),function(data){
console.log(data);
getFileByPath(path.join(__dirname,"./files/3.txt"),function(data){
console.log(data);
})
})
})
這種一層又一層的嵌套 即稱爲回調地獄
回調地獄即意爲大量使用了回調函數(即 將一個函數作爲參數傳遞給另個函數)並且有許多以 })
結尾的符號
這使得代碼看起來非常混亂
Promise解決回調地域問題的方式是將多層嵌套變成串聯式的函數調用
Promise其實是一個構造函數 可以new Promise()
然後得到一個Promise的實例
裏面有兩個重要的方法:resolve()
和reject()
其中 resolve()是成功後的回調函數 reject()是失敗後的回調函數
Promise構造函數的Prototype屬性上有個.then()
方法 因此 只要是Promise函數創建的實例都可以訪問.then()方法
Promise是一個容器 裏面保存着某個未來纔會結束的事件(異步操作)的結果
每當new了一個Promise實例之後 該實例就表示一個具體的異步操作
該異步操作的結果只有兩種狀態:成功 or 失敗
- Promise執行成功後需要在內部調用成功的回調函數
resolve()
將結果返回給調用者 - Promise執行失敗後需要在內部調用失敗的回調函數
reject()
將結果返回給調用者
由於Promise的實例是異步操作 因此內部拿到操作的結果後 無法使用return將結果返回給調用者 需要藉助回調函數(callback)將成功或失敗的結果返回給調用者
可在new出來的Promise實例上調用.then()方法 預先地爲該Promise異步操作指定成功和失敗後的回調函數(resolve()或是reject())
function test()
{
var promise=new Promise(function(){
// 異步函數
})
return promise;
}
test().then(function(){
})
Promise的特點:只要被創建立刻執行傳入的方法
每當new了一個Promise實例的時候 就會立即執行該異步操作中的代碼
也就是說 new的時候 不僅僅是得到一個Promise實例 還會立即調用給Promise構造方法傳遞的方法 執行該方法裏面的異步操作代碼
const fs=require("fs")
const path=require("path")
var promise=new Promise(function(){
fs.readFile(filePath,"utf-8",(err,dataStr)=>{
if (err)
{
throw err;
}
console.log(dataStr)
})
})
二、用法
通過.then()
指定成功和失敗的回調函數:
在如下的實例中:
執行getFileByPath()方法 進入方法體裏面 new了一個Promise實例並立即執行傳入的function
接下來 傳入的function還沒執行完 由於是異步的 立刻return 將promise實例返回到外面
然後在外面拿到了 在此時讀文件的方法還未執行完畢 通過.then()方法 指定了成功回調函數和失敗回調函數
然後 當文件讀取完畢 訪問了resolve(若成功)或reject(若失敗) 此時由於是外面的調用先於讀文件的方法
因此必定能訪問到resolve()和reject() 完美解決了異步無法獲取到數據的問題
const fs=require("fs")
const path=require("path")
function getFileByPath(filePath)
{
// 在傳入Promise的方法的參數列表中定義resolve和reject這兩個形參
var promise=new Promise(function(resolve,reject){
fs.readFile(filePath,"utf-8",(err,dataStr)=>{
if (err)
{
return reject(err);
}
resolve(dataStr);
})
})
// 將該promise對象返回出去
return promise;
}
var mypromise=getFileByPath(path.join(__dirname,"./files/1.txt"))
// 通過then()方法預先指定成功和失敗的方法的實參
// 由於是異步操作 因此是.then()先執行 並在.then()的參數裏具體化了resolve和reject 因此必定能調用成功
mypromise.then(function(data){
console.log(data)
},
function(err){
console.log(err.message)
}
)
將Promise簡化:
const fs=require("fs")
const path=require("path")
function getFileByPath(filePath)
{
return new Promise(function(resolve,reject){
fs.readFile(filePath,"utf-8",(err,dataStr)=>{
if (err)
{
return reject(err);
}
resolve(dataStr);
})
})
}
getFileByPath(path.join(__dirname,"./files/1.txt")).then(function(data){
console.log(data)
},
function(err){
console.log(err.message)
}
)
使用Promise解決回調地獄問題:
在.then()裏可以返回新的Promise實例 然後繼續用.then()進行下一步的操作
const fs=require("fs")
const path=require("path")
function getFileByPath(filePath)
{
return new Promise(function(resolve,reject){
fs.readFile(filePath,"utf-8",(err,dataStr)=>{
if (err)
{
return reject(err);
}
resolve(dataStr);
})
})
}
// 讀取文件1
getFileByPath(path.join(__dirname,"./files/1.txt"))
.then(function(data){
console.log(data)
// 讀取文件2
return getFileByPath(path.join(__dirname,"./files/2.txt"))
})
.then(function(data){
console.log(data)
// 讀取文件3
return getFileByPath(path.join(__dirname,"./files/3.txt"))
})
.then(function(data){
console.log(data)
})
Promise的異常捕獲
在Promise中 有兩種異常捕獲的方式:
一種是前面的Promise執行失敗了也不影響後續的Promise的正常執行
另一種是前面的Promise執行失敗了 後續的Promise都不執行
1、異常捕獲方式一:前面的Promise執行失敗了也不影響後續的Promise的正常執行
可爲每個Promise單獨地通過.then()指定失敗後的回調函數
若前面的Promise執行失敗 後續的Promise操作不會被終止 仍然執行
在.then()方法中的第二個參數位置傳遞一個回調方法 捕獲異常 進行處理 然後在裏面返回一個Promise對象給下一個.then()
getFileByPath(path.join(__dirname,"./files/a.txt"))
.then(function(data){
console.log(data)
// 讀取文件2
return getFileByPath(path.join(__dirname,"./files/2.txt"))
},function(err){
console.log(err.message)
// 返回一個新的Promise 不影響後續的操作執行
return getFileByPath(path.join(__dirname,"./files/2.txt"))
})
.then(function(data){
console.log(data)
// 讀取文件3
return getFileByPath(path.join(__dirname,"./files/3.txt"))
})
.then(function(data){
console.log(data)
})
2、異常捕獲方式二:一旦出現報錯 立即終止所有Promise的執行
若前面任何一個Promise執行失敗 立即終止所有Promise的執行 並立即進入catch()進行錯誤捕獲
在最後用.catch()
捕獲異常
getFileByPath(path.join(__dirname,"./files/1.txt"))
.then(function(data){
console.log(data)
// 讀取文件2
return getFileByPath(path.join(__dirname,"./files/a.txt"))
})
.then(function(data){
console.log(data)
// 讀取文件3
return getFileByPath(path.join(__dirname,"./files/3.txt"))
})
.then(function(data){
console.log(data)
})
.catch(function(err){
console.log(err.message)
})
在JQuery的Ajax中使用Promise:
JQuery發送Ajax請求也支持Promise
普通的AJAX:
<script>
$(function(){
$.ajax({
url:"localhost:8080/api/getData",
type:"get",
dataType:"json",
success:function(data)
{
console.log(data);
},
error:function(err)
{
console.log(err);
}
})
})
</script>
使用Promise:
往.then()
裏分別傳入兩個成功後和失敗後的回調函數
<script>
$(function(){
$.ajax({
url:"localhost:8080/api/getData",
type:"get",
dataType:"json"
})
.then(function(data){
console.log(data);
},
function(err){
console.log(err)
})
})
</script>