前言:
做過前端開發的都知道,JavaScript是單線程語言,瀏覽器只分配給JS一個主線程,用來執行任務,但是每次一次只能執行一個任務,這些任務形成一個任務隊列排隊等候執行;但是某些任務是比較耗時的,如網絡請求,事件的監聽,以及定時器,如果讓這些非常耗時的任務一一排隊等候執行,那麼程序執行效率會非常的低,甚至會導致頁面假死。因此,瀏覽器爲這些耗時的任務開闢了新的線程,主要包括http請求線程、瀏覽器事件觸發線程、瀏覽器定時觸發器,但是這些任務都是異步的,這就涉及到了前端開發的異步回調操作處理,前端處理異步回調操作用到的就是Async/Await和Promise。
而且在前端相關的面試的時候,面試官一般都會問到關於Promise相關的使用問題,甚至在筆試中也會出一些關於Promise和setTimeout的執行結果,這說明Promise的使用對於前端開發來說是非常重要的一個知識點。那麼本篇博文就來分享一下關於Promise的使用相關的知識點。
一、首先,要知道爲什麼要用Promise語法?
在介紹Promise
之前,首先來了解一下JavaScript的特性。搞前端開發的都知道JS是一個傳統的單線程編程,它裏面的程序運行都是同步的,只有一個主線程,但是隨着技術的發展,爲了解決前期的缺陷,引入了異步思想,也就是一個異步過程的執行將不再與原有的序列有順序關係,這就解決了同步執行引起的執行效率不高的缺陷。用一句話解釋:異步就是從主線程發射一個子線程來完成任務。
再來了解一下Promise,Promise是ES6新增加的,它是一個由ES6提供的類,其主要目的就是很好的處理複雜的異步任務,但是它不是任何瀏覽器都能支持,比如一些舊版本的瀏覽器就不支持,只有蘋果的Safari10和Windows的Edge14版本以上瀏覽器纔開始支持ES6特性的。
Promise作爲替代回調函數執行,作爲異步操作的處理方法;是JS異步執行時候,回調函數嵌套回調函數的這一問題的解決方法,Promise更簡潔地控制函數執行流程。Promise對象其實表示是一個異步操作的最終成敗,以及結果值,也就是一個代理值,是ES6中的一種異步回調解決方案。
Promise對象代理的值其實是未知的,狀態是動態可變的,因此Promise對象的狀態有三種:進行中、結束、失敗,它運行的時候,只能從進行中到失敗,或者是從進行中到成功。使用Promise對象只要是通過同步的表達形式來運行異步代碼。
- ①pending:初始狀態,既不成功,也不失敗;
- ②fulfilled:操作成功結束;
- ③rejected:操作失敗。
Promise屬於Es 新增的內置構造函數,可以直接調用。
英文意思是:承諾
有三種狀態:pending-等待態 resolved-成功態 rejected-失敗態
new的時候傳入一個執行(器函數)
=〉1 這個執行器會立即執行
=〉2 這個執行器接受兩個函數參數 分別是resolve和rejected
=〉3 調用resolve,會把promise狀態從pending—>resolved
調用reject,會把promise狀態從pending—>rejected
怎麼構造Promise?這裏簡單舉一個構造Promise的示例:
new Promise(function (resolve, reject) { // 要做的事情...});
通過上面新構造一個Promise
對象好像並沒有看出它是怎樣很好的處理複雜的異步任務的,那麼接下來就是Promise的核心操作。
二、接着,來了解一下回調地獄(Callback Hell)
回調地獄也叫回調嵌套或者函數混亂的調用,通俗點講就是:需要發送三個網絡請求,第三個請求依賴第二個請求的結果,第二個請求依賴第一個請求的結果。不斷增加的嵌套使用。
回調函數的弊病:
開發者閱讀起來很費神、喫力,不利於排查錯誤,更不能直接return,等等。
setTimeout(() => {
console.log(1)
setTimeout(() => {
console.log(2)
setTimeout(() => {
console.log(3)
},3000)
},2000)
},1000)
三、最後,也是本章的重頭戲,Promise的基本使用
Promise 構造函數只有一個參數,是一個函數,這個函數在構造之後會直接被異步運行,所以我們稱之爲起始函數。起始函數,也就是Promise的構造函數裏面有兩個參數:resolve和reject,該兩個參數表示的是異步操作的結果,也就是Promise成功或失敗的狀態。
當 Promise 被構造時,起始函數會被異步執行;resolve 和 reject 都是函數,其中調用 resolve 代表一切正常,reject 是出現異常時所調用的。
- ①異步操作成功,調用resolve函數,將Promise對象的狀態改爲fulfilled。
- ②異步操作失敗,調用rejected函數,將Promise對象的狀態改爲rejected。
Promise基本用法:
創建:
const p = new Promise((resolve, reject) => {
console.log(123);
resolve('我成功了');
// reject('我失敗了');
})
then方法使用:
// promise實例的then方法(then方法是異步的),當p的狀態是成功的時候,第一個回調函數會被執行,當p的狀態是失敗的時候, 第二個回調函數會被執行
p.then(res => {
console.log('SUCCESS',res);
},error => {
console.log('ERROR',error);
})
console.log(456);
//最終輸出結果如下: 123 456 SUCCESS
then的鏈式調用 p.then().then()....
// 第一個then到底執行哪個回調,取決於p的狀態
// 後面的then到底執行哪個回調 取決於上一次then執行的回調函數的返回值
// => 假如上一次then返回的是普通紙,數字、字符串、對象。。。 那麼本次then執行成功回調
// => 假如上一次then返回的是Promise對象,假如該Promise對象是成功態,本次then執行成功回調,失敗態,本次then執行失敗回調
// => 上一次then回調函數執行過程中出錯,直接走下一次then的失敗回調。
p.then((r) => {
console.log('aaa',r);
return 1; // 不寫return默認返回undefined return結果會作爲下一個then成功回調參數。
// reject(); 則走下一個then的失敗回調
}).then((r) => {
console.log('bbb',r)
})
catch方法使用:
// 失敗態的時候 會執行catch傳入的回調函數
p.catch(e => {
console.log('Fail',e);
});
all方法使用:
// 異步併發時,可以用all方法,並且返回結果是一個數組,數組結果的順序則是請求的先後順序,不會改變。
// 並且每個請求都成功纔會返回成功結果數組,否則會報錯
const p1 = new Promise((resolve,reject) => {
// 異步
setTimeout( ()=> {
resolve('success1');
},5000);
});
const p2 = new Promise((resolve,reject) => {
// 異步
setTimeout( ()=> {
resolve('success2');
},1000);
})
const p3 = new Promise((resolve,reject) => {
// 異步 xi
setTimeout( ()=> {
resolve('success3');
},1000);
})
Promise.all([p1,p2,p3]).then((res) => {
console.log(res); // ['success1','success2','success3']
})
async 和 await (Es7語法,Es6中已有提案)
異步流程 回調 —> promise —> generator —> async+await
// async是函數修飾關鍵字
// await 必須在async修飾的函數內部使用
// await 後面是一個promise對象纔有意義
async function fn() {
const res = await this.getList(); // this.getList()獲取列表數據的請求函數
console.log(res);
}
js實例:
//小明他老爸說:假設這次考試及格的話,給他買AJ,不及格,吊起來來打
//$.ajax({
// url: "去考試",
// success: function(res){
// if(res >= 60){
// console.log("給他買aj");
// }else{
// console.log("吊起來來打");
// }
// }
//});
// 小紅明他老爸說:假設這次考試及格的話,給她買5年模擬3年考試,不及格,帶它去看鯊魚
// $.ajax({
// url: "去考試",
// success: function(res){
// if(res >= 60){
// console.log("給她買5年模擬3年考試");
// }else{
// console.log("帶它去看鯊魚");
// }
// }
// });
function test(resolve, reject){
$.ajax({
url: "去考試",
success: function(res){
if(res >= 60){
resolve();
}else{
reject();
}
}
});
}
// test(function(){
// console.log("給他買aj");
// }, function(){
// console.log("吊起來來打");
// })
// Promise
var promise = new Promise(function(resolve, reject){
setTimeout(function(){
// 模擬語文分數
var res = 61;
if(res >= 60){
resolve("小明同學語文及格");
}else{
reject();
}
}, 2000)
})
var promise2 = new Promise(function(resolve, reject){
setTimeout(function(){
// 模擬數學分數
var res = 61;
if(res >= 60){
resolve();
}else{
reject();
}
}, 2000)
})
// 考語文
// promise.then(function(){
// console.log("給他買aj");
// // 先考完語文,再考數學
// promise2.then(function(){
// console.log("再買一雙aj");
// });
// }).catch(function(){
// console.log("吊起來來打");
// })
//使用async修飾函數,且使用await 修飾promise,用來替代promise.then().then()多次執行的問題,讓代碼更加簡潔
async function test(){
// 先考語文
var res = await promise;
console.log(res)
// 再考數學
var res2 = await promise2;
console.log(res2);
// ...其他科目考試
}
test();
又例如:
通過一個異步讀取文件的小栗子來對比下Promise和 async 的異同點
const fs = require('fs')
function readFile(fileName) {
return new Promise((resolve, reject) => {
fs.readFile(fileName, (err, data) => {
if(err) {
reject(err)
}
resolve(data.toString())
})
})
}
readFile('data/a.txt').then(res => console.log(res))
.catch(err => console.log(err))
readFile('data/b.txt').then(res => console.log(res))
.catch(err => console.log(err))
readFile('data/c.txt').then(res => console.log(res))
.catch(err => console.log(err))
async function read() {
let readA = await readFile('data/a.txt')
let readB = await readFile('data/b.txt')
let readC = await readFile('data/c.txt')
console.log(readA)
console.log(readB)
console.log(readC)
}
read()
最終的輸出結果
async 函數的多種使用形式
// 函數聲明
async function foo() {
// ....
}
// 函數表達式
let foo = async function() {
// ....
}
// 箭頭函數
let foo = async() => {}
// 對象的方法
let obj = {
name: 'Roger',
async foo() {
}
}
obj.foo().then(res => {
// ....
})
// 類的方法
class Student{
constructor(name, age) {
this.name = name
this.age = age
}
async say() {
return `My name is ${this.name}, I'm ${this.age} years old !`
}
}
let jim = new Student('Jim Green', 13)
jim.say().then(res => console.log(res)) // My name is Jim Green, I'm 13 years old !
// 休眠 ms 毫秒
function sleep(ms) {
return new Promise(resolve => {
setTimeout(resolve, ms)
})
}
async function print(ms) {
console.log('start... ...')
await sleep(ms)
console.log('end... ...')
}
print(1000)
(1)、async 函數內部 return語句返回的值,會成爲then方法回調函數的參數
async function foo() {
return 'hello world'
}
foo().then(res => console.log(res)) // hello world
async function bar() {
return new Error('Error... ...')
}
bar().then(res => console.log(res))
.catch(err => console.log(err)) // Error: Error... ...
async function baz() {
await new Promise(resolve => {
console.log('執行第一個異步操作')
setTimeout(resolve, 2000)
})
await new Promise(resolve => {
console.log('執行第二個異步操作')
setTimeout(resolve, 3000)
})
return '異步執行完畢再執行then方法'
}
baz().then(res => {console.log(res)})
[return_value] = await expression
表達式:一個 Promise對象或者任何要等待的值
返回值:返回 Promise對象的處理結果。如果等待的不是 Promise對象,則返回該值本身
await命令會暫停當前 async函數的執行,等待 Promise處理完成。如果 Promise正常處理,其回調的 resolve函數參數會作爲 await表達式的返回值,繼續執行 async函數。如果 Promise處理異常,await表達式會把 Promise的異常原因拋出
// 如果 await 命令後的表達式的值不是一個 Promise,則返回該值本身
async function foo() {
return await 123
}
foo().then(res => console.log(res)) // 123
// 如果 Promise 正常處理(fulfilled),其回調的resolve函數參數作爲 await 表達式的返回值
async function bar() {
let f = await new Promise((resolve, reject) => {
resolve('我是表達式的返回值')
})
console.log(f) // 我是表達式的返回值
return 'ending'
}
bar().then(res => {console.log(res)}) // ending
// 如果 Promise 處理異常(rejected),await 表達式會把 Promise 的異常原因拋出
async function baz() {
await new Promise((resolve, reject) => {
reject(new Error('出錯啦......'))
})
}
baz().then(res => console.log(res))
.catch(err => console.log(err)) // Error: 出錯啦......
async function foo() {
await Promise.reject('error')
return 'ending' // 未執行
}
foo().then(res => console.log(res))
// Uncaught (in promise) error
async function foo() {
try{
await Promise.reject('error')
} catch(e) {
}
return await Promise.resolve('執行完畢')
}
foo().then(res => console.log(res)) // 執行完畢
async function foo() {
await Promise.reject('error').catch(err => console.log(err))
return '執行完畢'
}
foo().then(res => console.log(res)) // 執行完畢
// 寫法一
let [foo, bar] = await Promise.all([getFoo(), getBar()]);
// 寫法二
let fooPromise = getFoo();
let barPromise = getBar();
let foo = await fooPromise;
let bar = await barPromise;