JS 異步與 Promise
本文寫于 2020 年 6 月 8 日
1. 同步與異步與回呼函式
Promise 現在是前端面試必考題呀,但是先不急著看 Promise,我們首先來看看什么是異步,
———— 所謂異步,就是不是同步(笑),
咳咳,假設我們去商場吃飯,但是商場的餐廳大家都知道,非常的火爆,常常需要排隊一個多小時,
這時候大家除非特別累了,不然一定不會原地等待,而是選擇去其他樓層轉一轉,
這就是異步啦,同步又是什么呢?
比如我們去銀行取錢,在作業人員將錢交給我們之前,我們必須站在視窗前等待 —— 直到拿到錢,
回到剛剛商場餐廳的例子,
當我們排了很久的隊,終于輪到了的時候,餐廳會如何通知呢?或者說餐廳不通知我們,我們該如何直到可以吃飯了呢?
有很多種方法嘛,比如每十分鐘回去看一眼,或者分一個隊友守在餐廳門口,等隊伍快排到的時候就給我們打電話,
前一種方法叫做輪詢,后一種方法就叫做回呼,
回呼回呼,就是回頭(一會兒)再呼叫你,
上代碼:
function fn1() {}
function fn2(fn) {
fn()
}
fn2(fn1)
我們先宣告一個 fn1 函式,然后宣告一個能接受一個函式作為引數的 fn2 函式,最后呼叫 fn2 函式,傳入 fn1 作為引數,
fn1 就是回呼函式,因為并不是我們呼叫的它,而是 fn2 呼叫的它,
一般來說,如果函式不是我們呼叫的,那就是一個回呼函式,
我們常見的回呼函式一般和這些東西有關:setTimeout, AJAX, AddEventListener,它們都是會呼叫別的函式,而被呼叫的函式就是回呼函式了,
那現在講明了“同步”、“異步”、“回呼”,請問三者之間有什么關系嗎?
回呼函式是異步函式的專屬嘛?當然不是啦,我們剛剛舉的例子不就是同步編程嗎?一樣用到了回呼函式,
還有很常見的 arr.forEach(item => item + 1),forEach 里面的函式,它也是一個同步函式,
所以說我們得到了這么兩條結論:
- 異步任務拿不到結果(比如商場餐廳排隊),一般需要依賴與回呼函式;
- 回呼函式可以在異步任務結束時執行,也可以在同步任務中使用,
2. 我們為什么需要 Promise?
剛剛我們說了異步編程,異步任務結束之后可以用回呼函式可以拿到最后的結果,
但是如果有倆結果呢?比如獲取成功和獲取失敗,
那就傳入兩個引數啰,Node.js 都這么用,
fs.readFile('./data.txt', (error, data) => {
if(error) {
console.log(error)
return
}
console.log(data.toString())
})
或者還可以用兩個回呼函式:
ajax('GET', '/data.json', (data) => {}, (error) => {})
但是這些方法其實都不有些不足,
比如:
- 名稱五花八門不規范,可以用 success+error;可以用 success+fail;可以用 done+fail……
- 回呼地獄,一層套一層套一層,無限縮進;
- 難以進行錯誤處理,
所以程式員必須解決這些問題,首先我們需要規范回呼的名字和順序;其次必然的,要拒絕回呼地獄,增強代碼可讀性;最后還要讓我們方便的捕捉錯誤,
于是就有了 Promise,
但是注意,Promise 不是 ES6 發布的時候才有的,
1976 年,就有人提出了 Promise 思想,前端只是結合了 Promise 與 JavaScript,制定了 Promise/A+規范
Promise 是異步編程的一種解決方案,比傳統的解決方案——回呼函式和事件——更合理和更強大,它由社區最早提出和實作,ES6 將其寫進了語言標準,統一了用法,原生提供了
Promise物件, ——《ES6 標準入門》
3. Promise 的基本用法
所謂
Promise,簡單說就是一個容器,里面保存著某個未來才會結束的事件(通常是一個異步操作)的結果,從語法上說,Promise 是一個物件,從它可以獲取異步操作的訊息,Promise 提供統一的 API,各種異步操作都可以用同樣的方法進行處理, ——《ES6 標準入門》
ES6 規定,Promise物件是一個建構式,用來生成Promise實體,
下面代碼創造了一個Promise實體:
const promise = new Promise((resolve, reject) => {
// ... some code
if (/* 異步操作成功 */){
resolve(value)
} else {
reject(value)
}
})
Promise建構式接受一個函式作為引數,該函式的兩個引數分別是resolve和reject,它們是兩個函式,由 JavaScript 引擎提供,不用自己部署,
resolve函式的作用是:將Promise物件的狀態從“未完成”變為“成功”(pending -> fulfilled),它在異步操作成功時呼叫,并將異步操作的結果,作為引數傳遞出去,
reject函式的作用是:將Promise物件的狀態從“未完成”變為“失敗”(pending -> rejected),在異步操作失敗時呼叫,并將異步操作報出的錯誤,作為引數傳遞出去,
Promise實體生成以后,可以用then方法分別指定resolved狀態和rejected狀態的回呼函式,
promise.then(
function (value) {
// success
},
function (error) {
// failure
}
);
then方法可以接受兩個回呼函式作為引數,第一個回呼函式是Promise物件的狀態變為resolved時呼叫,第二個回呼函式是Promise物件的狀態變為rejected時呼叫,其中,第二個函式是可選的,不一定要提供,這兩個函式都接受Promise物件傳出的值作為引數,
看不懂?沒關系,我們來做一個小模擬,
現在假設我們要吃飯,那么有三個步驟:
- 洗菜切菜做飯;
- 盛菜端飯坐下吃飯;
- 收拾桌子洗碗,
試想一下,如果沒有做飯,可以直接跳躍到第二步吃飯嗎?
可以,可以喝西北風,
所以這個程序是有順序的,必須上一步執行完成,才能順利進行下一步,
ES6 的寫法:
首先有一個狀態值,表示我們目前是在第一步第二步還是第三步:let state = 1
第一步,寫一個洗菜切菜做飯函式:
function step1(resolve, reject) {
console.log('洗菜!切菜!做飯!');
if (state == 1) {
resolve('洗菜切菜做飯complete!');
} else {
reject('洗菜切菜做飯Failed……');
}
}
這里看不懂沒關系,就當作是拋出一個訊息即可,
第二步,開始吃飯:
function step2(resolve, reject) {
console.log("吃飯啦!")
if(state == 1) {
resolve("吃飯成功!")
} else {
reject("吃飯失敗……")
}
}
第三步,吃完了該洗碗去了:
function step3(resolve, reject) {
console.log('吃完了洗碗!');
if (state == 1) {
resolve('洗完啦!');
} else {
reject('洗碗失敗……');
}
}
接下來,我們就需要寫Promise物件了:
new Promise(step1)
.then(function (val) {
console.log('val', val);
return new Promise(step2);
})
.then(function (val) {
console.log(val);
return new Promise(step3);
})
.then(function (val) {
console.log(val);
return val;
});
相信只要懂小學二年級英語的同學,就能大概看懂這段代碼的意思了:
new一個Promise物件,然后傳入一個函式,也就是第一步的洗菜函式;- 函式內部有一個
resolve和一個reject,分別是請求成功接收的訊息,和請求被拒絕后接收的訊息; - 接著是一個
then,他也傳入一個函式,接收上一步拋出的值,也就是resolve或者是reject; - 再
return一個新的Promise物件,用作下一步的操作,
4. Promise 的特點
Promise物件有以下兩個特點,
-
物件的狀態不受外界影響,
Promise物件代表一個異步操作,有三種狀態:pending(進行中)、fulfilled(已成功)和rejected(已失敗),只有異步操作的結果可以決定當前是哪一種狀態,任何其他操作都無法改變這個狀態——這就是Promise這個名字的由來,所謂“承諾”,即其他手段無法改變, -
一旦狀態改變,就不會再變,任何時候都可以得到這個結果,
Promise物件的狀態改變,只有兩種可能:從pending變為fulfilled;從pending變為rejected,只要這兩種情況發生,狀態就凝固了,不會再變了,會一直保持這個結果!這時就稱為 resolved(已定型),如果改變已經發生了,你再對Promise物件添加回呼函式,也會立即得到這個結果,這與事件(Event)完全不同,事件的特點是,如果你錯過了它,再去監聽,是得不到結果的,
Promise物件讓我們可以將異步操作以同步操作的流程表達出來,避免了“回呼地獄”,
并且此外,Promise物件提供統一的原生介面,使得 JS 控制異步操作更加容易!
但是Promise也有缺點:
- 無法取消
Promise,一旦新建它就會立即執行,無法中途取消(axios 可以取消); - 如果不設定回呼函式,
Promise內部拋出的錯誤,不會反應到外部; - 當處于
pending狀態時,無法得知目前進展到哪一個階段(剛剛開始還是即將完成),
(完)
轉載請註明出處,本文鏈接:https://www.uj5u.com/qiye/73780.html
標籤:JavaScript
