Promise晉級—徹底吃透

Promise

Promise晉級,須要的所有都在這javascript

主要內容:java

  1. promise基本實現原理
  2. promise 使用中難點(鏈式調用,API基本上返回都是一個新Promise,及參數傳遞)
  3. promise 對異常處理
  4. promise 簡單實現及規範
  5. es6 對promise更進一步掌握Async晉級——徹底吃透

參考:es6

30分鐘,讓你完全明白Promise原理web

阮一峯ES6入門ajax

JavaScript Promise:簡介json

0. 基本用法

基本的promise使用,讀本文須要瞭解基本的Promise使用。segmentfault

1. 兼容性

查看caniuse數組

Promise兼容性

查兼容性 基本上 主流瀏覽器支持沒有問題。promise

IE不兼容 問題,本文不予以處理,出門左轉,找谷哥。具體查看 babel,或者 本身實現一個Promise瀏覽器

2. ajax XMLHttpRequest封裝

//get 請求封裝
function get(url) {
  // Return a new promise.
  return new Promise(function(resolve, reject) {
    // Do the usual XHR stuff
    var req = new XMLHttpRequest();
    req.open('GET', url);

    req.onload = function() {
      // This is called even on 404 etc
      // so check the status
      if (req.status == 200) {
        // Resolve the promise with the response text
        resolve(req.response);
      }
      else {
        // Otherwise reject with the status text
        // which will hopefully be a meaningful error
        reject(Error(req.statusText));
      }
    };

    // Handle network errors
    req.onerror = function() {
      reject(Error("Network Error"));
    };

    // Make the request
    req.send();
  });
}
複製代碼

1. Promse API

Promise API 分爲 :MDN

  1. 靜態方法

  2. prototype上方法

    Promise.prototype.then() 來分析

    首先來看看 `Promise.prototype.then()`返回一個`Promise`,但`Promise`內部有返回值,且 返回值,能夠是個值,也可能就是一個新`Promise`
    
    *具體規則以下:*
    複製代碼
    • 若是then中的回調函數返回一個值,那麼then返回的Promise將會成爲接受狀態,而且將返回的值做爲接受狀態的回調函數的參數值。
    • 若是then中的回調函數拋出一個錯誤,那麼then返回的Promise將會成爲拒絕狀態,而且將拋出的錯誤做爲拒絕狀態的回調函數的參數值。
    • 若是then中的回調函數返回一個已是接受狀態的Promise,那麼then返回的Promise也會成爲接受狀態,而且將那個Promise的接受狀態的回調函數的參數值做爲該被返回的Promise的接受狀態回調函數的參數值。
    • 若是then中的回調函數返回一個已是拒絕狀態的Promise,那麼then返回的Promise也會成爲拒絕狀態,而且將那個Promise的拒絕狀態的回調函數的參數值做爲該被返回的Promise的拒絕狀態回調函數的參數值。
    • 若是then中的回調函數返回一個未定狀態(pending)的Promise,那麼then返回Promise的狀態也是未定的,而且它的終態與那個Promise的終態相同;同時,它變爲終態時調用的回調函數參數與那個Promise變爲終態時的回調函數的參數是相同的。

    上面是官方規則,神馬,具體白話就是 核心是 返回參數及返回promise的狀態

    參考:MDN

    是否是 以爲很暈,不要緊,能夠先看 下一節,看完後,再回過來看具體的說明

    /*then 回調中, 1. 返回是return function,則返回一個Promise 【參見對比3代碼】 2. 不是一個function,則 then 將建立一個沒有通過回調函數處理的新 Promise 對象,這個新 Promise 只是簡單地接受調用這個 then 的原 Promise 的終態做爲它的終態。(MDN中解釋)【參見對比1代碼】 3. 返回一個function,但沒有return ,則至關於 then(null) */
    //對比1 穿透問題 返回是'foo' 而不是 'bar'
    Promise.resolve('foo').then(Promise.resolve('bar')).then(function(result){
        console.log(result)
    })
    
    
    //對比2 打印undefined
    Promise.resolve('foo').then(function(){Promise.resolve('bar')}).then(function(result){
        console.log(result)
    })
     
    
    //對比3 返回 'bar'
    Promise.resolve('foo').then(function() {
        return Promise.resolve('bar')
    }).then(function(result) {
        console.log(result)
    })
    複製代碼

2. Prmise 鏈式調用

鏈式調用

  1. 核心就是 then catch 等方法返回一個Promise
  2. 鏈式 調用數據傳遞(注意)

1. 值傳遞問題

簡單例子

//正常狀態
const promise1 = new Promise((resolve, reject) => {
    resolve('0000')//
})
promise1.then(result => {
    console.log(result) //0000
	   return '1111';//相似於 return Promise.resolve('1111'); 參數是data,promise 狀態時 resolve
}).then(data => {
    console.log(data) // 1111
})
複製代碼

一個實際的例子:(拿來大神的例子JavaScript Promise:簡介

get('story.json').then(function(response) {
  console.log("Success!", response);
})
複製代碼
//這裏的 response 是 JSON,可是咱們當前收到的是其純文本。也能夠設置XMLHttpRequest.responseType =json
get('story.json').then(function(response) {
  return JSON.parse(response);
}).then(function(response) {
  console.log("Yey JSON!", response);
})
複製代碼
//因爲 JSON.parse() 採用單一參數並返回改變的值,所以咱們能夠將其簡化爲:
get('story.json').then(JSON.parse).then(function(response) {
  console.log("Yey JSON!", response);
})
複製代碼
function getJSON(url) {
  return get(url).then(JSON.parse);
}
//getJSON() 仍返回一個 promise,該 promise 獲取 URL 後將 response 解析爲 JSON。
複製代碼

2. 異步操做隊列

上面至今是return 值,直接調用 下一下then就OK了。

但若是return Promise,則?

Promise.resolve(111).then(function(d){
	console.log(d);
	return Promise.resolve(d+111);//返回promise
}).then(function(d2){
	console.log(d2);
})
// 111,222
複製代碼

3. 並行問題forEach處理

當多個異步並行執行時,每一個異步代碼執行時間不定,因此多個異步執行結束時間沒法肯定(沒法肯定結束完時間)。

因此須要特殊處理。

//forEach 順便沒法保證
var arrs = [1,2,3,4];
var p = function(d){
	return new Promise((resolve)=>{
       setTimeout(()=>{
			resolve(d);
		},Math.random()*1000);//由於異步執行時間沒法確認
    });
};
arrs.forEach(function(arr){
  p(arr).then((d)=>{
    console.log(d);
  })
});
複製代碼
//使用 Promise.all 來讓返回有序
var arrs = [1,2,3,4];
var p = function(d){
	return new Promise((resolve)=>{
       setTimeout(()=>{
			resolve(d);
		},Math.random()*1000);//由於異步執行時間沒法確認
    });
};
var ps = [];
arrs.forEach(function(arr){
  ps.push(p(arr));
});
Promise.all(ps).then(values=>{
  console.log(values);//[1,2,3,4]
})
複製代碼

4. 基本實現原理—實現一個簡單Promise

本身手擼一個簡單的Promise

1. 版本1—極簡實現

//版本1 極簡實現
function Promise1(fn) {
 var value = null,
     callbacks = [];  //callbacks爲數組,由於可能同時有不少個回調

 this.then = function (onFulfilled) {
     callbacks.push(onFulfilled);
     return this;//支持鏈式調用 Promise.then().then
 };

 function resolve(value) {
     callbacks.forEach(function (callback) {
         callback(value);
     });
 }

 fn(resolve);
}
//Test 對上面實現,寫一個簡單的測試
new Promise1(function(resolve){
 setTimeout(function(){
     resolve(1);
 },100);
}).then(function(d){
 console.log(d);
})
//1
複製代碼

2. 版本2—加入延時機制

//上面版本1 可能致使問題
//在then註冊回調以前,resolve就已經執行了
new Promise1(function(resolve){
    console.log(0)
	resolve(1);
}).then(function(d){
   console.log(d);
})
// 1 不會打印
複製代碼
//版本2 解決
function Promise1(fn) {
    var value = null,
        callbacks = [];  //callbacks爲數組,由於可能同時有不少個回調

    this.then = function (onFulfilled) {
        callbacks.push(onFulfilled);
        return this;//支持鏈式調用 Promise.then().then
    };

    function resolve(value) {
       setTimeout(function(){
        callbacks.forEach(function (callback) {
            callback(value);
        }),0});
    }

    fn(resolve);
}
複製代碼

3. 版本3—狀態

Promise有三種狀態pendingfulfilledrejected ,且狀態變化時單向的。

具體細節就是 在then,resolve中加狀態判斷,具體代碼略

4. Promises/A+

具體 Promise實現有一套官方規範,具體參見Promises/A+

5. finnaly 實現

Promise.prototype.finally = function (callback) {
  let P = this.constructor;
  return this.then(
    value => P.resolve(callback()).then(() => value),
    reason => P.resolve(callback()).then(() => { throw reason })
  );
};
複製代碼

6. 異常處理

異常分類:

  1. 同步異常
  2. 異步異常 沒法try-catch 獲得
  3. 多層Promise嵌套,獲異常取具體的一個promise異常,而不是所有

1. Promise 異常處理基本套路

基本處理異常中,有兩種方案then(undefined, func)catch()

then(undefined, func)catch()不一樣,具體參見代碼方案3

//方案1 使用 Promise.prototype.catch()來catch
const promise1 = new Promise((resolve, reject) => {
    reject('no')// 
})
promise1.then(result => {
    console.log(result) // 永遠不會執行
}).catch(error => {
    console.log(error) // no
})
複製代碼
//方案2 使用 Promise.prototype.then()中第二個參數 來處理
const promise1 = new Promise((resolve, reject) => {
    reject('no')// 
})
promise1.then(result => {
    console.log(result) // 永遠不會執行
},error => {
    console.log(error) // no
})
複製代碼
//方案2 (方案1 方案2 對比)
var promise2 = new Promise((resolve, reject) => {
    resolve('yes')// 
})
promise2.then(result => {
    throw new Error('then');
    console.log(result) 
},error => {
    console.log('1111',error) // no
}).catch(error=>{
   console.log('2222',error)// 最終 err在此處被捕獲,而不是 then 中
})
複製代碼

2. 異常不一樣分類

Promise可能遇到的異常種類

//1.異常 reject()
const promise1 = new Promise((resolve, reject) => {
    reject('no')// 
})
promise1.then(result => {
    console.log(result) // 永遠不會執行
}).catch(error => {
    console.log(error) // no
})

複製代碼
//2.異常 顯示throw
const promise1 = new Promise((resolve, reject) => {
    throw Error('no')
})
promise1.then(result => {
    console.log(result) // 永遠不會執行
}).catch(error => {
    console.log(error) // 
})
複製代碼
//3.執行異常
const promise1 = new Promise((resolve, reject) => {
    aaaa;
})
promise1.then(result => {
    console.log(result) // 永遠不會執行
}).catch(error => {
    console.log(error) // 
})
複製代碼

3. 異常鏈式調用

asyncThing1().then(function() {
  return asyncThing2();
}).then(function() {
  return asyncThing3();
}).catch(function(err) {
  return asyncRecovery1();
}).then(function() {
  return asyncThing4();
}, function(err) {
  return asyncRecovery2();
}).catch(function(err) {
  console.log("Don't worry about it");
}).then(function() {
  console.log("All done!");
})
複製代碼

上述代碼的流程圖形式:

流程圖形式

// promise鏈式調用,catch住異常後,後面就不會處理異常了
Promise.reject().then(()=>{
  console.log(2222);
},(err)=>{
	console.log(333,err)
	return err})
.catch((err)=>{
  console.log(1111,err);
})
//333 undefined ,沒有打印 1111
複製代碼
//若是 在鏈式調用中,then 第二個參數 catch住了異常,沒有return Promise.reject()則後續鏈式調用返回rosolve狀態pormise
Promise.reject()
   .then(()=>{
      console.log(111);
    },(err)=>{
        console.log(111,err) //reject 
        return err;
    }).then((data)=>{
        console.log(222,data);//resolve 執行
    },(err)=>{
      console.log(222,err); //未執行
    })
//4444 沒有執行 1111
複製代碼

4. 異常丟失

不少狀況下,promise沒法捕獲異常

場景1 macrotask 隊列中拋出異常:

//場景1
//永遠不要在 macrotask 隊列中拋出異常,由於 macrotask 隊列脫離了運行上下文環境,異常沒法被當前做用域捕獲。
function fetch(callback) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
             throw Error('用戶不存在')
        })
    })
}

fetch().then(result => {
    console.log('請求處理', result) // 永遠不會執行
}).catch(error => {
    console.log('請求處理異常', error) // 永遠不會執行
})

// 程序崩潰
// Uncaught Error: 用戶不存在

/* 參考 做者:黃子毅 連接:https://www.jianshu.com/p/78dfb38ac3d7 來源:簡書 簡書著做權歸做者全部,任何形式的轉載都請聯繫做者得到受權並註明出處。 */
複製代碼
//解決場景1 怎麼解決,由於setTimeout 是macrotask任務,執行上下文徹底不一樣
/** 如何解決? 調用reject */
function fetch() {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            reject('收斂一些')
        })
    })
}
fetch().then((resolve, reject) => {
    console.log('resolve');
}).catch(error => {
    console.log('捕獲異常', error) // 捕獲異常 收斂一些
})
複製代碼

場景二 Promise 狀態只能改變一次

//異常丟失
   const promise2 = new Promise((resolve, reject) => {
       reject('no')
       console.log('reject after')
     throw Error('no') //異常丟失
   })
   promise1.then(result => {
       console.log(result) // 永遠不會執行
   }).catch(error => {
       console.log('err',error) // no
   }).catch(error => {
       console.log('err2',error) // 也沒法捕獲異常
   })
複製代碼

7.async

async是 Promise 更高一層的封裝,具體參見深刻淺出Async

相關文章
相關標籤/搜索