這裏咱們先囉嗦一下Promise的概念:es6
Promise 是異步編程的一種解決方案,比傳統的解決方案——回調函數和事件——更合理和更強大。它由社區最先提出和實現,ES6 將其寫進了語言標準,統一了用法,原生提供了Promise對象。編程
那如何實現一個符合規範的Promise呢?json
咱們知道promise中共有三種狀態 pending 過渡態 fulfilled 完成態 rejected 失敗態數組
promise狀態改變只有兩種可能promise
過渡態=>成功態bash
過渡態 => 失敗態dom
過程不可逆 沒法相互轉化異步
這裏來借用一張圖片更容易理解異步編程
let promise = new Promise((resolve, reject) => {
//這裏放入咱們要執行的函數,多是同步,也多是異步, 這裏咱們就來寫一個異步的執行
setTimeout(() => {
resolve('hello');
})
})
promise.then(data => {
console.log(data);
}, err => {console.log(err)})
複製代碼
上面代碼表示咱們new一個promise實例,並異步執行 這裏經過調用then方法,咱們成功獲得告終果函數
觀察原生promise用法,咱們能夠發現,在new Promise時候傳入了一個函數,這個函數在規範中的叫法是exector 執行器 看到這裏,咱們先有一個大概思路,構建一個本身的Promise構造函數
// 這裏咱們建立了一個構造函數 參數就是執行器
function Promise(exector) {
}
複製代碼
好的,第一步完成, 重點來了,這個Promise內部到底幹了什麼呢 能夠看到,原生的exector中傳入了兩個參數,第一個參數執行會讓promise狀態變爲resolve, 也就是成功, 第二個執行會讓函數變爲reject狀態,也就是失敗
而且這兩個形參執行以後均可以傳入參數,咱們繼續完善代碼 咱們將這兩個形參的函數封裝在構造函數內部
// 這裏咱們建立了一個構造函數 參數就是執行器
function Promise(exector) {
// 這裏咱們將value 成功時候的值 reason失敗時候的值放入屬性中
let self = this;
this.value = undefined;
this.reason = undefined;
// 成功執行
function resolve(value) {
self.value = value;
}
// 失敗執行
function reject(reason) {
self.reason = reason;
}
exector(resolve, reject);
}
複製代碼
這裏問題來了,咱們知道,promise的執行過程是不可逆的,resolve和rejeact之間也不能相互轉化, 這裏,咱們就須要加入一個狀態,判斷當前是否在pending過程,另外咱們的執行器可能直接報錯,這裏咱們也須要處理一下.
// 這裏咱們建立了一個構造函數 參數就是執行器
function Promise(exector) {
// 這裏咱們將value 成功時候的值 reason失敗時候的值放入屬性中
let self = this;
// 這裏咱們加入一個狀態標識
this.status = 'pending';
this.value = undefined;
this.reason = undefined;
// 成功執行
function resolve(value) {
// 判斷是否處於pending狀態
if (self.status === 'pending') {
self.value = value;
// 這裏咱們執行以後須要更改狀態
self.status = 'resolved';
}
}
// 失敗執行
function reject(reason) {
// 判斷是否處於pending狀態
if (self.status === 'pending') {
self.reason = reason;
// 這裏咱們執行以後須要更改狀態
self.status = 'rejected';
}
}
// 這裏對異常進行處理
try {
exector(resolve, reject);
} catch(e) {
reject(e)
}
}
複製代碼
這裏先留個小坑,一會咱們回頭來補上
好了,Promise基本就是這樣,是否是很簡單,這裏咱們先實現一個簡易版,後面的功能會逐步添加進去,不要心急,繼續日後看
new Promise以後咱們怎麼去改變promise對象的狀態呢, 經過前面原生的用法咱們瞭解到,須要使用then方法, then方法分別指定了resolved狀態和rejeacted狀態的回調函數 那怎麼知道使用哪一個回調函數呢,咱們剛不是在構造函數內部定義了status麼,這裏就用上啦,上代碼
// 咱們將then方法添加到構造函數的原型上 參數分別爲成功和失敗的回調
Promise.prototype.then = function(onFulfilled, onRejected) {
// 獲取下this
let self = this;
if (this.status === 'resolved') {
onFulfilled(self.value);
}
if (this.status === 'rejected') {
onRejected(self.reason);
}
}
複製代碼
ok,咱們如今能夠本身運行試試
let promise = new Promise((resolve, reject) => {
resolve("haha");
})
promise.then(data => {
console.log(data); //輸出 haha
}, err=> {
console.log(err);
})
// 屢次調用
promise.then(data => {
console.log(data); //輸出 haha
}, err=> {
console.log(err);
})
複製代碼
上面能夠注意到, new Promise中的改變狀態操做咱們使用的是同步,那若是是異步呢,咱們平時遇到的基本都是異步操做,該如何解決?
這裏咱們須要在構造函數中存放兩個數組,分別保存成功回調和失敗的回調 由於能夠then屢次,因此須要將這些函數放在數組中 代碼以下:
// 這裏咱們建立了一個構造函數 參數就是執行器
function Promise(exector) {
// 這裏咱們將value 成功時候的值 reason失敗時候的值放入屬性中
let self = this;
// 這裏咱們加入一個狀態標識
this.status = 'pending';
this.value = undefined;
this.reason = undefined;
// 存儲then中成功的回調函數
this.onResolvedCallbacks = [];
// 存儲then中失敗的回調函數
this.onRejectedCallbacks = [];
// 成功執行
function resolve(value) {
// 判斷是否處於pending狀態
if (self.status === 'pending') {
self.value = value;
// 這裏咱們執行以後須要更改狀態
self.status = 'resolved';
// 成功以後遍歷then中成功的全部回調函數
self.onResolvedCallbacks.forEach(fn => fn());
}
}
// 失敗執行
function reject(reason) {
// 判斷是否處於pending狀態
if (self.status === 'pending') {
self.reason = reason;
// 這裏咱們執行以後須要更改狀態
self.status = 'rejected';
// 成功以後遍歷then中失敗的全部回調函數
self.onRejectedCallbacks.forEach(fn => fn());
}
}
// 這裏對異常進行處理
try {
exector(resolve, reject);
} catch(e) {
reject(e)
}
}
// then 改造
Promise.prototype.then = function(onFulfilled, onRejected) {
// 獲取下this
let self = this;
if (this.status === 'resolved') {
onFulfulled(self.value);
}
if (this.status === 'rejected') {
onRejected(self.reason);
}
// 若是異步執行則位pending狀態
if(this.status === 'pending') {
// 保存回調函數
this.onResolvedCallbacks.push(() => {
onFulfilled(self.value);
})
this.onRejectedCallbacks.push(() => {
onRejected(self.reason)
});
}
}
// 這裏咱們能夠再次實驗
let promise = new Promise((resolve, reject) => {
setTimeout(() => {
if(Math.random() > 0.5) {
resolve('成功');
} else {
reject('失敗');
}
})
})
promise.then((data) => {
console.log('success' + data);
}, (err) => {
console.log('err' + err);
})
複製代碼
這裏要開始重點了,千萬不要錯過,經過以上代碼,咱們實現了一個簡易版的promise,說簡易版是由於咱們的then方法只能調用一次,並無實現原生promise中的鏈式調用。
那鏈式調用是如何實現的呢?
這裏咱們須要回顧下promiseA+規範,經過查看規範和阮一峯的es6講解能夠了解到
then方法返回的是一個新的Promise實例(注意,不是原來那個Promise實例)。所以能夠採用鏈式寫法,即then方法後面再調用另外一個then方法。
這裏咱們看一段原生promise代碼
getJSON("/posts.json").then(function(json) {
return json.post;
}).then(function(post) {
// ...
});
複製代碼
上面的代碼使用then方法,依次指定了兩個回調函數。 第一個回調函數完成之後,會將返回結果做爲參數,傳入第二個回調函數。
採用鏈式的then,能夠指定一組按照次序調用的回調函數。這時,前一個回調函數,有可能返回的仍是一個Promise對象(即有異步操做),這時後一個回調函數,就會等待該Promise對象的狀態發生變化,纔會被調用。
另外經過原生的promise咱們還能夠發現,上一次的成功或者失敗在返回值是一個普通類型數據的時候,都走向了下一次then的成功回調,咱們能夠繼續改造then方法
Promise.prototype.then = function(onFulfilled, onRejected) {
// 獲取下this
let self = this;
// 由於then方法返回的是一個promise,這裏咱們新建一個promise
let promise2 = new Promise((resolve, reject) => {
if (this.status === 'resolved') {
//獲取回調的返回值
try {
// 當執行成功回調的時候 可能會出現異常,那就用這個異常做爲promise2的錯誤的結果
let x = onFulfilled(self.value);
//執行完當前成功回調後返回結果多是promise
resolvePromise(promise2,x,resolve,reject);
} catch (e) {
reject(e);
}
}
if (this.status === 'rejected') {
//獲取回調的返回值
try {
let x = onRejected(self.reason);
resolvePromise(promise2,x,resolve,reject);
} catch (e) {
reject(e);
}
}
// 若是異步執行則位pending狀態
if(this.status === 'pending') {
// 保存回調函數
this.onResolvedCallbacks.push(() => {
//獲取回調的返回值
try {
let x = onFulfilled(self.value);
resolvePromise(promise2,x,resolve,reject);
} catch (e) {
reject(e);
}
})
this.onRejectedCallbacks.push(() => {
//獲取回調的返回值
try {
let x = onRejected(self.reason);
resolvePromise(promise2,x,resolve,reject);
} catch (e) {
reject(e);
}
});
}
})
return promise2;
}
複製代碼
這裏咱們看下新的then函數有什麼變化,咱們一步一步分析,首先,新建了一個promise並返回,這裏是根據原生promise文檔得知: then方法返回的是一個新的Promise實例(注意,不是原來那個Promise實例)。所以能夠採用鏈式寫法,即then方法後面再調用另外一個then方法。
這裏理解以後咱們繼續看,內部咱們又獲取了本次then方法成功或者失敗回調以後的返回值,賦值給變量x,這裏就會出現幾種狀況,變量x可能爲普通值,也可能爲一個promise
咱們定義了一個resolvePromise函數,將then返回的promise, 本次成功或者失敗的返回值,已經then返回promise的兩個參數傳輸這個函數中,進行一些判斷,具體實現以下:
function resolvePromise(promise2,x,resolve,reject){
// promise2和函數執行後返回的結果是同一個對象
if(promise2 === x){
return reject(new TypeError('Chaining cycle'));
}
// x多是一個promise 或者是一個普通值
if(x!==null && (typeof x=== 'object' || typeof x === 'function')){
try{
let then = x.then;
// 取對象上的屬性 怎麼能報異常呢?(這個promise不必定是本身寫的 多是別人寫的 有的人會亂寫)
// x可能仍是一個promise 那麼就讓這個promise執行便可
// {then:{}}
// 這裏的邏輯不僅僅是本身的 還有別人的 別人的promise 可能既會調用成功 也會調用失敗
if(typeof then === 'function'){
then.call(x,y=>{ // 返回promise後的成功結果
// 遞歸直到解析成普通值爲止
// 遞歸 可能成功後的結果是一個promise 那就要循環的去解析
resolvePromise(promise2,y,resolve,reject);
},err=>{ // promise的失敗結果
reject(err);
});
}else{
resolve(x);
}
}catch(e){
reject(e);
}
}else{ // 若是x是一個常量
resolve(x);
}
}
複製代碼
看的這裏是否是有點蒙圈,不要緊,咱們繼續分析這個實現。
首選進入函數內部,咱們判斷promise2是否是等於x, 這個至關於判斷上次then的返回值是否是成功和回調的返回值,這樣就是陷入死循環,舉個例子:
let p = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('hello');
})
})
let p2 = p.then(data => {
return p2;
})
複製代碼
這種寫法就會陷入一個死循環 因此要避免這種狀況發生。 好的,繼續往下看,剛纔說到x多是一個普通值,也多是一個promise,因此函數內部就要作一個判斷,是不是一個promise, 若是返回的是一個promise,那麼須要繼續執行這個promise,這裏用了遞歸。 平時使用promise時候咱們也會注意到,各類promise庫可能會混用,因此內部對這個then的類型進行了判斷。
Ok,到這裏是否是理解了一些了,咱們繼續往下看,咱們知道同一個Promise內部的狀態是沒法相互轉化的,這裏須要在內部作一個判斷。
function resolvePromise(promise2,x,resolve,reject){
// promise2和函數執行後返回的結果是同一個對象
if(promise2 === x){
return reject(new TypeError('Chaining cycle'));
}
let called;
// x多是一個promise 或者是一個普通值
if(x!==null && (typeof x=== 'object' || typeof x === 'function')){
try{
let then = x.then; // 取對象上的屬性 怎麼能報異常呢?(這個promise不必定是本身寫的 多是別人寫的 有的人會亂寫)
// x可能仍是一個promise 那麼就讓這個promise執行便可
// {then:{}}
// 這裏的邏輯不僅僅是本身的 還有別人的 別人的promise 可能既會調用成功 也會調用失敗
if(typeof then === 'function'){
then.call(x,y=>{ // 返回promise後的成功結果
// 遞歸直到解析成普通值爲止
if(called) return; // 防止屢次調用
called = true;
// 遞歸 可能成功後的結果是一個promise 那就要循環的去解析
resolvePromise(promise2,y,resolve,reject);
},err=>{ // promise的失敗結果
if(called) return;
called = true;
reject(err);
});
}else{
resolve(x);
}
}catch(e){
if(called) return;
called = true;
reject(e);
}
}else{ // 若是x是一個常量
resolve(x);
}
}
複製代碼
咱們加入一個called變量,防止互相轉化。 代碼寫到這裏是否是就完了? 固然沒有,細心的同窗會發現,原生promise還有一個用法
let p = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('hello');
})
})
p.then().then(data => {
console.log(data);
throw new Error('e');
}).then().then(null, err => {
console.log(err);
})
複製代碼
這種用法會發生值穿透,當上一個then函數沒有調用成功和失敗回調的時候,值會傳遞進下一次then調用。
這個怎麼實現的呢,其實很簡單,咱們只須要判斷每次then調用的時候是否傳入了成功或者失敗的回調,沒有回調,就繼續返回上輪then成功或者失敗傳入的值。 咱們還了解到,then方法的回調都是異步執行的,這裏咱們簡單用定時器模仿下,固然內部實現可不是這麼簡單。這裏僅做爲簡單實現
代碼以下
Promise.prototype.then = function (onFulfilled, onRejected) {
onFulfilled = typeof onFulfilled === 'function'?onFulfilled:val=>val;
onRejected = typeof onRejected === 'function'?onRejected: err=>{throw err}
let self = this;
let promise2;
promise2 = new Promise((resolve, reject) => {
if (self.status === 'resolved') {
setTimeout(()=>{
try {
let x = onFulfilled(self.value);
resolvePromise(promise2,x,resolve,reject);
} catch (e) {
reject(e);
}
},0)
}
if (self.status === 'rejected') {
setTimeout(()=>{
try {
let x = onRejected(self.reason);
resolvePromise(promise2,x,resolve,reject);
} catch (e) {
reject(e);
}
},0)
}
if (self.status === 'pending') {
self.onResolvedCallbacks.push(() => {
setTimeout(()=>{
try {
let x = onFulfilled(self.value);
resolvePromise(promise2,x,resolve,reject);
} catch (e) {
reject(e);
}
},0)
});
self.onRejectedCallbacks.push(() => {
setTimeout(()=>{
try {
let x = onRejected(self.reason);
resolvePromise(promise2,x,resolve,reject);
} catch (e) {
reject(e);
}
},0)
});
}
});
return promise2
}
複製代碼
大工告成。
Promise.resove().then()
Promise.reject().then()
複製代碼
咱們一個一個來實現,就先看上面直接在Promise類上調用成功和失敗 咱們能夠這麼寫
Promise.reject = function(reason){
return new Promise((resolve,reject)=>{
reject(reason);
})
}
Promise.resolve = function(value){
return new Promise((resolve,reject)=>{
resolve(value);
})
}
複製代碼
catch呢, 至關於直接走入下一次then的失敗回調
Promise.prototype.catch = function(onRejected){
// 默認不寫成功
return this.then(null,onRejected);
};
複製代碼
Promise.all方法用於將多個 Promise 實例,包裝成一個新的 Promise 實例。 具體用法能夠參考es6文檔,這就不具體再說用法
// 傳入一個promise數組
Promise.all = function(promises){
// 返回執行後的結果
return new Promise((resolve,reject)=>{
let arr = [];
let i = 0;
function processData(index,data){
arr[index] = data;
// 判斷是否所有成功
if(++i == promises.length){
resolve(arr);
}
}
for(let i = 0;i<promises.length;i++){
promises[i].then(data=>{ // data是成功的結果
//將每次執行成功後的結果傳入函數
processData(i,data);
},reject);
}
})
}
複製代碼
race就更簡單了。
Promise.race = function(promises){
return new Promise((resolve,reject)=>{
for(let i = 0;i<promises.length;i++){
promises[i].then(resolve,reject);
}
})
}
複製代碼
這裏咱們就已經實現了promise常見的一些功能,這裏須要多看幾遍加深記憶。