盜火的普羅米修斯
曾經我在哪本書上看過這樣的介紹,Promise 的英文是從希臘語的直譯Prometheus演變過來,Promise也就是代表著先知 未來的意思。在希臘神話中,是最具智慧的神明之一,最早的泰坦巨神後代,名字有“先見之明”(Forethought)的意思。泰坦十二神伊阿佩託斯與名望女神克呂墨涅的兒子。普羅米修斯不僅創造了人類,給人類盜來了火,還教會了他們許多知識和技能。
後來由於給人類帶來了火種,他吩咐火神給普羅米修斯最嚴厲的懲罰。每天還要派一直鷹去啄他的肝臟,夜晚的時候肝臟又會重新長出來,夜以繼日的承受折磨。
非同步函式 Promise
我個人覺得Promise 函式跟 普羅米修斯很像,都是代表著處理未來的事情的先知。既然能處理未來的事情,那就代表著它擁有非常的能力,聽我慢慢吹來呀~
首先來看看官方的吹牛文件解釋:
所謂Promise,簡單說就是一個容器,裡面儲存著某個未來才會結束的事件(通常是一個非同步操作)的結果。從語法上說,Promise 是一個物件,從它可以獲取非同步操作的訊息。Promise 提供統一的 API,各種非同步操作都可以用同樣的方法進行處理。
你看沒錯吧,是不是很像一個先知可以處理未來的事情?既然是神仙肯定是我們們凡人管不了的,為啥管不了那?就體現在它三個內部狀態上:
物件的狀態不受外界影響。Promise物件代表一個非同步操作,有三種狀態:pending(進行中)、fulfilled(已成功)和rejected(已失敗)只有非同步操作的結果,可以決定當前是哪一種狀態,任何其他操作都無法改變這個狀態。這也是Promise這個名字的由來,它的英語意思就是“承諾”,表示其他手段無法改變。
看見了嗎,敲黑板劃重點,神仙已經決定的東西就不能改變。而且一旦new Promise物件就不能取消,講究~
那如何使用那?下面來看一個簡單的例子:
const promise = new Promise (function(resolve, reject) { //這是先知啊,先知
if (/* 非同步操作成功 */){
resolve(value); // resolve就是這事兒先知同意了,你就幹就完了
} else {
reject(error); // reject就是這事兒先知不同意,白b扯了
}
});
複製程式碼
注意上面的事情其實有沒有發生?沒有,先知嘛預知未來的事情,什麼時候發生那?當然先知同意的時候也就是說狀態變成resolved。只要一resolved 馬上就可以then了,劃重點 敲黑板 then 就是這玩意。
promise.then(function(value) {
// 妥妥的這個已經辦了,下面你想咋地吧?可以連式操作無限then下去
}, function(error) {
// 失敗了消停的把錯誤資訊打出來把
console.log("錯誤資訊:"+error)
});
複製程式碼
那麼坑人的玩意來了,請看下面程式碼,誰先列印?
let promise = new Promise(function(resolve, reject) {
console.log(`Promise`);
resolve();
});
promise.then(function() {
console.log(`resolved.`);
});
console.log(`Hi!`);
複製程式碼
上面程式碼中,Promise 新建後立即執行,所以首先輸出的是Promise。然後,then方法指定的回撥函式,將在當前指令碼所有同步任務執行完才會執行,所以resolved最後輸出。那麼接下來這段
你要是能直接寫出答案,基本Promise就不用看了,根據上面的理論,Promise一旦建立馬上就執行,但是我們們說了 resolve 是未來的狀態,所以第二行程式碼先等一等,三行程式碼也不用看了,肯定是四行程式碼先執行,然後往下走 第六行執行,回過頭來 第三行直接成功了,所以會接下去執行,最後一個不用我說了吧,第二行程式碼需要走個完成的resolve狀態,把1傳遞給第五行的t引數,所以肯定是最後執行的。
概念吹完了,來看勢力
妥了,接下來看一個用Promise物件實現的 Ajax 操作的例項:
const getJSON = function(url) {
const promise = new Promise(function(resolve, reject){
const handler = function() {
if (this.readyState !== 4) {
return;
}
if (this.status === 200) {
resolve(this.response);
} else {
reject(new Error(this.statusText));
}
};
const client = new XMLHttpRequest();
client.open("GET", url);
client.onreadystatechange = handler;
client.responseType = "json";
client.setRequestHeader("Accept", "application/json");
client.send();
});
return promise;
};
getJSON("/posts.json").then(function(json) {
console.log(`Contents: ` + json);
}, function(error) {
console.error(`出錯了`, error);
});
複製程式碼
是不是很簡單,一點都不難,只是把ajax的操作流程放到了先知的內部,讓先知幫你獲取各種成功失敗的狀態!結合實際,我們在工作中可能一次要請求好幾個介面的資料,Promise提供了一個更加簡單的方法
const p = Promise.all([p1, p2, p3]);
複製程式碼
很好理解,all 的引數代表這一個可執行的任務佇列,只要裡面有任務就可以往下執行,也就是我們們訪問的多個api集合組成的陣列。
// 生成一個Promise物件的陣列
const promises = [2, 3, 5, 7, 11, 13].map(function (id) {
return getJSON(`/post/` + id + ".json"); //ID 作為引數 變成動態請求
});
Promise.all(promises).then(function (posts) {
// ...
}).catch(function(reason){
// ...
});
複製程式碼
還有一個類似 all的api Promise.race,Promise.race方法的引數與Promise.all方法一樣,如果不是 Promise 例項,就會先呼叫下面講到的Promise.resolve方法,將引數轉為 Promise 例項,再進一步處理
const p = Promise.race([
fetch(`/resource-that-may-take-a-while`),
new Promise(function (resolve, reject) {
setTimeout(() => reject(new Error(`request timeout`)), 5000)
})
]);
p
.then(console.log)
.catch(console.error);
複製程式碼
上面程式碼中,如果 5 秒之內fetch方法無法返回結果,變數p的狀態就會變為rejected,從而觸發catch方法指定的回撥函式。
妥妥滴,既然到這裡我估計你應該懂了,其實任何技術都不難,都能拆分成若干個簡單點。任意簡單的點組合起來就又複雜了,正所謂大道至簡,相信萬事萬物都是簡單的原理,懷著敬畏的心理去學習,終究會得到自己的收穫。