摘要:等待十秒后代碼才算執行完畢常見用法將定時器和異步操作放在一起,如果定時器先觸發,則認為超時,告知用戶。
Promise
介紹:
用于異步計算
將異步操作隊列化,按照期望的順序執行,返回符合預期的結果
可以在對象之間傳遞和操作promise,幫助我們處理隊列
由于promise是控制異步操作的,所以先來介紹一下在promise之前異步操作的常見語法。
事件偵聽與響應
回調函數(例如ajax請求回調)
異步回調的問題:
回調地獄問題(一個回調嵌入一個回調,特別是一些數據庫操作和文件操作 , 難以維護)
無法正常使用 return 和 throw
異步回調的回調函數都是在一個新棧中,所以在現在的棧無法獲取到先前棧的信息。之前的棧也捕獲不到當前棧拋出的錯誤,所以在異步回調中無法正常使用try catch正常處理錯誤。
在異步回調中經常需要在外層回調中去定義一些變量給內層回調使用。
talk is cheap , show me the code
const path = require("path"); const fs = require("fs"); //尋找最大文件的函數 function findLargest(dir,callback){ fs.readdir(dir,function(err,files){ if(err) return callback(err); //[錯誤使用回調來處理] let count = files.length; //獲取文件長度 let errored = false; //是否錯誤 let stats = []; //遍歷文件夾下的所有文件 files.forEach(file => { fs.stat(path.join(dir,file),(err,stat) =>{ if(errored) return; if(err){ errored = true; return callback(err); } stats.push(stat); if(--count === 0){ let largest = stats .filter(function(stat){ console.log("-----"); console.log(stat.isFile()); return stat.isFile(); }) //先判斷是否是文件 .reduce(function(prev,next){ //判斷大小 if(prev.size > next.size) { return prev; } return next; }); callback(null,files[stats.indexOf(largest)]) } }) }) }) } findLargest("../blog/blogDemo/移動端滾動詳解demo",function(err,filename){ if(err) return console.error(err); console.log("largest file was:",filename); })
上面就是一個查找最大文件的例子,其中有許多回調帶來的問題。接下來我們先回歸主題,學習一些promise的使用,然后使用promise來改寫這個例子。
promise詳解
new Promise( /*實例化Promise時傳入一個執行器,也就是一個函數*/ function(resolve,reject){ //異步操作放在這里 resolve(); //處理成功,修改實例化的promise對象的狀態為fulfilled reject(); //處理失敗,修改實例化的promise對象的狀態為rejected } ) .then(function A(){ //成功之后的處理,即調用resolve()就執行A中的內容 },function B(){ //失敗之后的處理,即調用reject()或者拋出了錯誤,就執行B中的內容 })
promise有三個狀態:
pending 【待定】初始狀態
fulfilled 【實現】操作成功
rejected 【否決】操作失敗
promise的狀態一發生改變,立馬調用.then()中的響應函數處理后續步驟,如果then()中返回了一個新的promise實例,則繼續循環下去。
promise常用的場景:
console.log("start"); new Promise(function(resolve,reject){ setTimeout(function(){ //定時器模擬異步 resolve("hello"); //修改promise狀態調用then中的第一個函數 },2000); }).then((value)=>{ console.log(value); //接收resolve傳來的值 return new Promise(function(resolve){ //該then()返回一個新的promise實例,后面可以繼續接then setTimeout(function(){ resolve("world"); //修改新promise的狀態,去調用then },3000) }) }).then((value)=>{ console.log(value); }) //輸出結果: /* 立即輸出 start 兩秒輸出 hello 再三秒 world */
上面我們在 then() 函數中返回的是一個新的promise,如果返回的不是一個新的promise會怎樣呢?依然是上面的代碼,稍作修改。
console.log("start"); new Promise(function(resolve,reject){ setTimeout(function(){ resolve("hello"); },2000); }).then((value)=>{ console.log(value); (function(){ return new Promise(function(resolve){ setTimeout(function(){ resolve("world"); },3000) }) })(); return false; }).then((value)=>{ console.log(value); }) /* 結果: 立即輸出 start 兩秒輸出 hello 三秒輸出 flase */
根據上面的運行結果來看,如果在一個then()中沒有返回一個新的promise,則return 什么下一個then就接受什么,在上面的實例代碼中return的是false,下一個then中接受到的value就是false,如果then中沒有return,則默認return的是undefined.
注意:then中return Promise必須是在then函數的作用域中return,不能在其他函數作用域中return,無效。上面的例子中return Promise就是在一個立即執行函數中返回的,所以無效。
.then()中包含.then()的嵌套情況
then()的嵌套會先將內部的then()執行完畢再繼續執行外部的then();在多個then嵌套時建議將其展開,將then()放在同一級,這樣代碼更清晰。
console.log("start"); new Promise((resolve,reject)=>{ setTimeout(function(){ console.log("step"); resolve(110); },1000) }) .then((value)=>{ return new Promise((resolve,reject)=>{ setTimeout(function(){ console.log("step1"); resolve(value); },1000) }) .then((value)=>{ console.log("step 1-1"); return value; }) .then((value)=>{ console.log("step 1-2"); return value; }) }) .then((value)=>{ console.log(value); console.log("step 2"); }) /* start step step1 step 1-1 step 1-2 110 step 2 */ //展開之后的代碼 console.log("start"); new Promise((resolve,reject)=>{ setTimeout(function(){ console.log("step"); resolve(110); },1000) }) .then((value)=>{ return new Promise((resolve,reject)=>{ setTimeout(function(){ console.log("step1"); resolve(value); },1000) }) }) .then((value)=>{ console.log("step 1-1"); return value; }) .then((value)=>{ console.log("step 1-2"); return value; }) .then((value)=>{ console.log(value); console.log("step 2"); })
錯誤處理
promise處理錯誤有兩種方式,一種是發現錯誤執行then中的第二個回調函數來處理錯誤,一種是.catch()來處理錯誤
注意:拋出ERROR時,只有在執行函數的頂層拋出后面的catch才會接受到。這里如果在setTimeout中拋出錯誤,catch和then中的錯誤處理函數是接受不到的
//1.根據錯誤執行then中第二個回調來處理錯誤 new Promise((resolve,reject)=>{ setTimeout(function(){ //只要出現了錯誤或者調用了reject,就可以在then的第二個函數中獲取到 reject("err"); },1000) }).then((value)=>{ console.log(value); }, //出錯之后的執行函數 (err)=>{ console.log("出錯了"); console.log(err); }) /* 出錯了 err */ //2.根據catch來獲取錯誤,拋出err或者執行reject()都會在catch中獲取到錯誤信息 new Promise((resolve,reject)=>{ //只要出現了錯誤,就可以在then的第二個函數中獲取到 setTimeout(function(){ reject("一個錯誤"); },1000) }).then((value)=>{ console.log(value); }).catch(err=>{ console.log("錯誤信息:"+err); }) /* 錯誤信息:一個錯誤 */
更推薦使用catch的方式進行處理錯誤,因為catch能獲取到之前所有then中出現的錯誤
catch和then的連用
如果每一步都有可能出現錯誤,那么就可能出現catch后面接上then的情況。上代碼
new Promise((resolve,reject)=>{ resolve(); }) .then(value=>{ console.log("done 1"); throw new Error("done 1 error"); }) .catch(err=>{ console.log("錯誤信息1:"+err); }) .then(value=>{ console.log("done 2"); }) .catch(err=>{ console.log("錯誤信息2:"+err); }) /* done 1 錯誤信息1:Error: done 1 error done 2 說明catch后面會繼續執行then,catch返回的也是一個promise實例 */ new Promise((resolve,reject)=>{ resolve(); }) .then(value=>{ console.log("done 1"); throw new Error("done 1 error"); }) .catch(err=>{ console.log("錯誤信息1:"+err); throw new Error("catch error"); }) .then(value=>{ console.log("done 2"); }) .catch(err=>{ console.log("錯誤信息2:"+err); }) /* done 1 錯誤信息1:Error: done 1 error 錯誤信息2:Error: catch error 如果在catch中也拋出了錯誤,則后面的then的第一個函數不會執行,因為返回的promise狀態已經為rejected了 */
總的來說,catch之后可以接then,catch也是返回的一個promise對象。如果catch中出現錯誤,則promise狀態修改成reject,否則為fullfilled狀態
Promise.all()
將多個Promise批量執行,所有的Promise都完畢之后返回一個新的Promise。
接收一個數組作為參數
數組中可以是Promise實例,也可以是別的值,只有Promise會等待狀態的改變
所有子Promise完成,則該Promise完成,并且返回值是參數數組中所有Promise實例的結果組成的數組
有任何一個Promise失敗,則該Promise失敗,返回值是第一個失敗的Promise的結果
console.log("here we go"); Promise.all([1,2,3]) .then(all=>{ console.log("1: " + all); return Promise.all([function(){ console.log("ooxx"); },"xxoo",false]) }) .then(all=>{ console.log("2: " + all); let p1 = new Promise(resolve=>{ setTimeout(function(){ resolve("I"m p1"); },1500) }); let p2 = new Promise(resolve=>{ setTimeout(function(){ resolve("I"m p2"); },2000) }); return Promise.all([p1,p2]); }) .then(all=>{ console.log("3: "+all); let p1 = new Promise((resolve,reject)=>{ setTimeout(function(){ resolve("P1"); },1000) }) let p2 = new Promise((resolve,reject)=>{ setTimeout(function(){ reject("P2"); },3000) }) let p3 = new Promise((resolve,reject)=>{ setTimeout(function(){ reject("P3"); },2000) }) return Promise.all([p1,p2,p3]); }) .then(all=>{ console.log("all: " + all); }) .catch(err=>{ console.log("Catch:" + err); }) /* here we go 1: 1,2,3 2: function(){ console.log("ooxx"); },xxoo,false 3: I"m p1,I"m p2 Catch:P3 證明了上面的四點。 */
Promise.race()
和Promise.all()差不多,區別就是傳入的數組中有一個Promise完成了則整個Promise完成了。
let p1 = new Promise(resolve=>{ setTimeout(function(){ resolve("p1"); },10000); }) let p2 = new Promise(resolve=>{ setTimeout(function(){ resolve("p2"); },1000); }) Promise.race([p1,p2]) .then((value)=>{ console.log(value); }) /* p1 1s之后輸出 。。 等待十秒后代碼才算執行完畢 */
常見用法:
將定時器和異步操作放在一起,如果定時器先觸發,則認為超時,告知用戶。
let p1 = new Promise(resolve=>{ $.ajax({ success:function(result){ resolve(result); } }); //異步操作 }) let p2 = new Promise(resolve=>{ setTimeout(function(){ resolve("timeout"); },10000); }) Promise.race([p1,p2]) .then(value=>{ if(value === "timeout"){ alert("請求超時"); } })
將回調包裝為Promise
好處:1.可讀性好 2. 返回的結果可以放在任意Promise隊列
// 將nodejs中fs模塊的readDir和readFile方法包裝為Promise //FileSystem.js const fs = require("fs"); module.exports = { readDir: function(path,options){ return new Promise(resolve=>{ fs.readdir(path,options,(err,files)=>{ if(err){ throw err; } resolve(files); }) }) }, readFile: function(path,options){ return new Promise(resolve=>{ fs.readFile(path,options,(err,content)=>{ if(err) throw err; resolve(content); }) }) } } //test.js const fs = require("./FileSystem"); fs.readFile("./test.txt","utf-8") .then(content=>{ console.log(content); })
尋找最大文件Promise改版
const fs = require("fs"); const path = require("path"); const FileSystem = require("./FileSystem"); //用上面封裝的FileSystem function findLargest(dir) { return FileSystem .readDir(dir, "utf-8") .then(files => { return Promise.all(files.map(file=>{ return new Promise(resolve =>{ fs.stat(path.join(dir,file),(err,stat)=>{ if err throw err; if(stat.isDirectory()){ return resolve({ size: 0 }); } stat.file = file; resolve(stat); }); }); })); }) .then( stats =>{ let biggest = stats.reduce((memo,stat)=>{ if(memo.size < stat.size){ return stat; } return memo; }); return biggest.file; }) }
我的文章都可以在我的gitbook上找到。歡迎star哈哈!gitbook地址
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/95765.html
摘要:本身就是的語法糖。類似于后面代碼會等內部代碼全部完成后再執行打印結果操作符用于等待一個對象。它只能在異步函數中使用。參考附在版本位中是可以直接使用的。持續更新中來點顆吧 async await本身就是promise + generator的語法糖。 本文主要講述以下內容 async awiat 實質 async await 主要特性 async await 實質 下面使用 pro...
摘要:本身就是的語法糖。類似于后面代碼會等內部代碼全部完成后再執行打印結果操作符用于等待一個對象。它只能在異步函數中使用。參考附在版本位中是可以直接使用的。持續更新中來點顆吧 async await本身就是promise + generator的語法糖。 本文主要講述以下內容 async awiat 實質 async await 主要特性 async await 實質 下面使用 pro...
摘要:本身就是的語法糖。類似于后面代碼會等內部代碼全部完成后再執行打印結果操作符用于等待一個對象。它只能在異步函數中使用。參考附在版本位中是可以直接使用的。持續更新中來點顆吧 async await本身就是promise + generator的語法糖。 本文主要講述以下內容 async awiat 實質 async await 主要特性 async await 實質 下面使用 pro...
摘要:現在不會用都不好意思說自己是前端,為什么火起來,一句話解決了回調嵌套和執行順序問題最重要的我感覺是解決順序問題。 現在不會用Promise都不好意思說自己是前端,Promise為什么火起來,一句話解決了回調嵌套和執行順序問題最重要的我感覺是解決順序問題。 不過開始寫之前我們先看看,promise怎么解決問題,怎么用。列舉一個順序加載圖片demo: //需求 加載三張圖片 img1,im...
閱讀 2342·2021-11-23 09:51
閱讀 1144·2021-11-22 13:52
閱讀 3617·2021-11-10 11:35
閱讀 1196·2021-10-25 09:47
閱讀 3000·2021-09-07 09:58
閱讀 1067·2019-08-30 15:54
閱讀 2823·2019-08-29 14:21
閱讀 3033·2019-08-29 12:20