Promise知識(shí)總結(jié)

本文目錄:

  • 1.什么是回調(diào)函數(shù)?回調(diào)函數(shù)有什么缺點(diǎn)
  • 2.Promise是什么,可以手寫實(shí)現(xiàn)一下嗎
  • 3.手寫簡(jiǎn)單版的Promise
  • 4.then,catch,finally
  • 5.手寫promise.all
  • 6.手寫promise.race
  • 7.Promise里都是微任務(wù)嗎
  • 8.async await 和 promise 的區(qū)別與聯(lián)系
  • 9.Promise中用了什么設(shè)計(jì)模式

1.什么是回調(diào)函數(shù)?回調(diào)函數(shù)有什么缺點(diǎn)

回調(diào)函數(shù)是一段可執(zhí)行的代碼段,它作為一個(gè)參數(shù)傳遞給其他的代碼,其作用是在需要的時(shí)候方便調(diào)用這段(回調(diào)函數(shù))代碼。
在JavaScript中函數(shù)也是對(duì)象的一種,同樣對(duì)象可以作為參數(shù)傳遞給函數(shù),因此函數(shù)也可以作為參數(shù)傳遞給另外一個(gè)函數(shù),這個(gè)作為參數(shù)的函數(shù)就是回調(diào)函數(shù)。

const btnAdd = document.getElementById('btnAdd');
btnAdd.addEventListener('click', function clickCallback(e) {
    // do something useless
});

在本例中,我們等待id為btnAdd的元素中的click事件,如果它被單擊,則執(zhí)行clickCallback函數(shù)?;卣{(diào)函數(shù)向某些數(shù)據(jù)或事件添加一些功能。
回調(diào)函數(shù)有一個(gè)致命的弱點(diǎn),就是容易寫出回調(diào)地獄(Callback hell)。假設(shè)多個(gè)事件存在依賴性:

setTimeout(() => {
    console.log(1)
    setTimeout(() => {
        console.log(2)
        setTimeout(() => {
            console.log(3)
        },3000)
    },2000)
},1000)

這就是典型的回調(diào)地獄,以上代碼看起來不利于閱讀和維護(hù),事件一旦多起來就更是亂糟糟,所以在es6中提出了Promise和async/await來解決回調(diào)地獄的問題。當(dāng)然,回調(diào)函數(shù)還存在著別的幾個(gè)缺點(diǎn),比如不能使用 try catch 捕獲錯(cuò)誤,不能直接 return。

2.Promise是什么,可以手寫實(shí)現(xiàn)一下嗎

Promise,翻譯過來是承諾,承諾它過一段時(shí)間會(huì)給你一個(gè)結(jié)果。從編程講Promise 是異步編程的一種解決方案。下面是Promise在MDN的相關(guān)說明:
Promise 對(duì)象是一個(gè)代理對(duì)象(代理一個(gè)值),被代理的值在Promise對(duì)象創(chuàng)建時(shí)可能是未知的。它允許你為異步操作的成功和失敗分別綁定相應(yīng)的處理方法(handlers)。這讓異步方法可以像同步方法那樣返回值,但并不是立即返回最終執(zhí)行結(jié)果,而是一個(gè)能代表未來出現(xiàn)的結(jié)果的promise對(duì)象。
一個(gè) Promise有以下幾種狀態(tài):
pending: 初始狀態(tài),既不是成功,也不是失敗狀態(tài)。
fulfilled: 意味著操作成功完成。
rejected: 意味著操作失敗。
這個(gè)承諾一旦從等待狀態(tài)變成為其他狀態(tài)就永遠(yuǎn)不能更改狀態(tài)了,也就是說一旦狀態(tài)變?yōu)?fulfilled/rejected 后,就不能再次改變??赡芄饪锤拍畲蠹也焕斫釶romise,我們舉個(gè)簡(jiǎn)單的栗子;
假如我有個(gè)女朋友,下周一是她生日,我答應(yīng)她生日給她一個(gè)驚喜,那么從現(xiàn)在開始這個(gè)承諾就進(jìn)入等待狀態(tài),等待下周一的到來,然后狀態(tài)改變。如果下周一我如約給了女朋友驚喜,那么這個(gè)承諾的狀態(tài)就會(huì)由pending切換為fulfilled,表示承諾成功兌現(xiàn),一旦是這個(gè)結(jié)果了,就不會(huì)再有其他結(jié)果,即狀態(tài)不會(huì)在發(fā)生改變;反之如果當(dāng)天我因?yàn)楣ぷ魈影?,把這事給忘了,說好的驚喜沒有兌現(xiàn),狀態(tài)就會(huì)由pending切換為rejected,時(shí)間不可倒流,所以狀態(tài)也不能再發(fā)生變化。
上一條我們說過Promise可以解決回調(diào)地獄的問題,沒錯(cuò),pending 狀態(tài)的 Promise 對(duì)象會(huì)觸發(fā) fulfilled/rejected 狀態(tài),一旦狀態(tài)改變,Promise 對(duì)象的 then 方法就會(huì)被調(diào)用;否則就會(huì)觸發(fā) catch。我們將上一條回調(diào)地獄的代碼改寫一下:

new Promise((resolve,reject) => {
     setTimeout(() => {
            console.log(1)
            resolve()
        },1000)
}).then((res) => {
    setTimeout(() => {
            console.log(2)
        },2000)
}).then((res) => {
    setTimeout(() => {
            console.log(3)
        },3000)
}).catch((err) => {
console.log(err)
})

其實(shí)Promise也是存在一些缺點(diǎn)的,比如無法取消 Promise,錯(cuò)誤需要通過回調(diào)函數(shù)捕獲。

3.手寫簡(jiǎn)單版的Promise

promise手寫實(shí)現(xiàn),面試夠用版:

function myPromise(executor) {
  var _this = this;
  this.onFulfilled = []; //成功的回調(diào)
  this.onRejected = []; //失敗的回調(diào)
  this.state = "PENDING"; //狀態(tài)
  this.value = undefined; //成功結(jié)果
  this.reason = undefined; //失敗原因
  function resolve(value) {
    if (_this.state === "PENDING") {
      _this.state = "FULFILLED";
      _this.value = value;
      _this.onFulfilled.forEach((fn) => fn(value));
    }
  }
  function reject(reason) {
    if (_this.state === "PENDING") {
      _this.state = "REJECTED";
      _this.reason = reason;
      _this.onRejected.forEach((fn) => fn(reason));
    }
  }
  try {
    executor(resolve, reject);
  } catch (e) {
    reject(e);
  }
}
// 定義鏈?zhǔn)秸{(diào)用的then方法
myPromise.prototype.then = function (onFullfilled, onRejected) {
  if (this.state === "FULFILLED") {
    typeof onFulfilled === "function" && onFulfilled(this.value);
  }
  if (this.state === "REJECTED") {
    typeof onRejected === "function" && onRejected(this.reason);
  }
  if (this.state === "PENDING") {
    typeof onFulfilled === "function" &&
      this.onFulfilled.push(onFulfilled);
    typeof onRejected === "function" && this.onRejected.push(onRejected);
  }
};
var myP = new myPromise((resolve, reject) => {
  console.log("執(zhí)行");
  setTimeout(() => {
    reject(3);
  }, 1000);
});
myP.then(
  (res) => {
    console.log(res);
  },
  (err) => {
    console.log(err);
  }
);

4.then,catch,finally

then 方法 的 第一個(gè)參數(shù)是 resolved 狀態(tài)對(duì)應(yīng)的回調(diào)函數(shù), 第二個(gè)參數(shù)(可選) 是 rejected 狀態(tài)對(duì)應(yīng)的回調(diào)函數(shù),then方法返回的是一個(gè)新的 Promise 實(shí)例.(注意, 不是原來那個(gè)Promise實(shí)例), 因此可以采用鏈?zhǔn)綄懛? 即 then 方法后面再調(diào)用另一個(gè) then 方法.

getJSON("/posts.json").then(function(json) {
  return json.post;
}).then(function(post) {
  // ...
});

Promise.prototype.catch() 方法是 `.then(null,rejection) 或.then(undefined,rejection)的別名, 用于指定發(fā)生錯(cuò)誤時(shí)的回調(diào)函數(shù).

getJSON('/posts.json').then(function(posts) {
  // ...
}).catch(function(error) {
  // 處理 getJSON 和 前一個(gè)回調(diào)函數(shù)運(yùn)行時(shí)發(fā)生的錯(cuò)誤
  console.log('發(fā)生錯(cuò)誤!', error);
});

上面代碼中,getJSON()方法返回一個(gè) Promise 對(duì)象,如果該對(duì)象狀態(tài)變?yōu)閞esolved,則會(huì)調(diào)用then()方法指定的回調(diào)函數(shù);如果異步操作拋出錯(cuò)誤,狀態(tài)就會(huì)變?yōu)閞ejected,就會(huì)調(diào)用catch()方法指定的回調(diào)函數(shù),處理這個(gè)錯(cuò)誤。另外,then()方法指定的回調(diào)函數(shù),如果運(yùn)行中拋出錯(cuò)誤,也會(huì)被catch()方法捕獲。
finally()方法用于指定不管 Promise 對(duì)象最后狀態(tài)如何,都會(huì)執(zhí)行的操作。該方法是 ES2018 引入標(biāo)準(zhǔn)的。

promise
.then(result => {···})
.catch(error => {···})
.finally(() => {···});

上面代碼中,不管promise最后的狀態(tài),在執(zhí)行完then或catch指定的回調(diào)函數(shù)以后,都會(huì)執(zhí)行finally方法指定的回調(diào)函數(shù)。
finally方法的回調(diào)函數(shù)不接受任何參數(shù),這意味著沒有辦法知道,前面的 Promise 狀態(tài)到底是fulfilled還是rejected。這表明,finally方法里面的操作,應(yīng)該是與狀態(tài)無關(guān)的,不依賴于 Promise 的執(zhí)行結(jié)果。

5.手寫promise.all

promise.all方法:當(dāng)參數(shù)中的promise有一個(gè)失敗了就直接返回失敗的結(jié)果,返回第一個(gè)失敗的結(jié)果,都成功返回所有的參數(shù)結(jié)果
思路:
返回一個(gè)新的promise,并遍歷調(diào)用傳入的promise組成的數(shù)組參數(shù),利用Promise.resolve()方法獲取成功執(zhí)行的所有promise的數(shù)量和數(shù)組參數(shù)的長(zhǎng)度進(jìn)行比對(duì),如果長(zhǎng)度小于參數(shù),則代表有失敗的結(jié)果,否則就是全部成功。

function promiseAll(promises){
  // 返回一個(gè)promise實(shí)例
  return new Promise((resolve, reject) => {
    // 做一個(gè)判斷參數(shù)是否是數(shù)組
    if(!Array.isArray(promises)){
      return reject(new TypeError('arguments must be Array'))
     }
   let count = 0,
       newValues = new Array(promise.length)  // 接收新的結(jié)果參數(shù) 建立一個(gè)偽數(shù)組
    for(let i = 0; i < promises.length; i++){ 
   // 運(yùn)用promise特性 只會(huì)有一個(gè)狀態(tài)
      Promise.resolve(promises[i])
      .then(res = > {
        count++
        newValues[i] = res // 把每次返回成功的數(shù)據(jù)添加到數(shù)組中
       if(count === promises.length){ // 數(shù)據(jù)接收完成
           resolve(newValues)  
        }
      }, rej = >  reject(rej))
     }
   }) 
}

成功的時(shí)候返回的是一個(gè)結(jié)果數(shù)組,而失敗的時(shí)候則返回最先被reject失敗狀態(tài)的值。,需要注意的是,Promise.all獲得的成功結(jié)果的數(shù)組里面的數(shù)據(jù)順序和Promise.all接收到的數(shù)組順序是一致的。

6.手寫promise.race

Promse.race就是賽跑的意思,意思就是說,Promise.race([p1, p2, p3])里面哪個(gè)結(jié)果獲得的快,就返回那個(gè)結(jié)果,不管結(jié)果本身是成功狀態(tài)還是失敗狀態(tài)。

function promiseRace(arrays){
    if(!Array.isArray(arrays))
    return 'not Array'
    return new Promise((resolve,reject)=>{
    for(var i = 0; i < arrays.length; i++){
       Promise.resolve(arrays[i]).then((value)=>{
                resolve(value)
        }, (err)=>{reject(err)})
    }
  })
}

7.Promise里都是微任務(wù)嗎

Promise 中只有涉及到狀態(tài)變更后才需要被執(zhí)行的回調(diào)才算是微任務(wù),比如說 then、 catch 、finally ,其他所有的代碼執(zhí)行都是宏任務(wù)(同步執(zhí)行)。

8.async await 和 promise 的區(qū)別與聯(lián)系

async await 和 promise 都是異步編程解決方案。
Promise的寫法只是回調(diào)函數(shù)的改進(jìn),使用then方法,只是讓異步任務(wù)的兩段執(zhí)行更清楚而已。Promise的最大問題是代碼冗余,請(qǐng)求任務(wù)多時(shí),一堆的then,也使得原來的語義變得很不清楚。
async搭配await是ES7提出的,它的實(shí)現(xiàn)是基于Promise,通過同步方式的寫法,使得代碼更容易閱讀。
async/await的優(yōu)勢(shì)在于處理 then 的調(diào)用鏈,能夠更清晰準(zhǔn)確的寫出代碼,并且也能優(yōu)雅地解決回調(diào)地獄問題。當(dāng)然也存在一些缺點(diǎn),因?yàn)?await 將異步代碼改造成了同步代碼,如果多個(gè)異步代碼沒有依賴性卻使用了 await 會(huì)導(dǎo)致性能上的降低。

9.Promise中用了什么設(shè)計(jì)模式

觀察者模式 在軟件設(shè)計(jì)中是一個(gè)對(duì)象,維護(hù)一個(gè)依賴列表,當(dāng)任何狀態(tài)發(fā)生改變自動(dòng)通知它們。
發(fā)布-訂閱模式是一種消息傳遞模式,消息的發(fā)布者(Publishers)一般將消息發(fā)布到特定消息中心,訂閱者(Subscriber)可以按照自己的需求從消息中心訂閱信息,跟消息隊(duì)列挺類似的。

觀察者設(shè)計(jì)模式的代碼實(shí)現(xiàn)


// 觀察者設(shè)計(jì)模式
class Observer {
  constructor () {
    this.observerList = [];
  }
 
  subscribe (observer) {
    this.observerList.push(observer)
  }
 
  notifyAll (value) {
    this.observerList.forEach(observe => observe(value))
  }
}

發(fā)布-訂閱設(shè)計(jì)模式的代碼實(shí)現(xiàn)


// 發(fā)布訂閱
class EventEmitter {
  constructor () {
    this.eventChannel = {}; // 消息中心
  }
 
  // subscribe
  on (event, callback) {
    this.eventChannel[event] ? this.eventChannel[event].push(callback) : this.eventChannel[event] = [callback]
  }
 
  // publish
  emit (event, ...args) {
    this.eventChannel[event] && this.eventChannel[event].forEach(callback => callback(...args))
  }
 
  // remove event
  remove (event) {
    if (this.eventChannel[event]) {
      delete this.eventChannel[event]
    }
  }
 
  // once event
  once (event, callback) {
    this.on(event, (...args) => {
      callback(...args);
      this.remove(event)
    })
  }
}

從代碼中也能看出他們的區(qū)別,觀察者模式不對(duì)事件進(jìn)行分類,當(dāng)有事件時(shí),將通知所有觀察者。發(fā)布-訂閱設(shè)計(jì)模式對(duì)事件進(jìn)行了分類,觸發(fā)不同的事件,將通知不同的觀察者。所以可以認(rèn)為后者就是前者的一個(gè)升級(jí)版,對(duì)通知事件做了更細(xì)粒度的劃分。
發(fā)布-訂閱和觀察者在異步中的應(yīng)用
觀察者模式


const observer = new Observer();
observer.subscribe(value => {
  console.log("第一個(gè)觀察者,接收到的值為:");
  console.log(value)
});
observer.subscribe(value => {
  console.log("第二個(gè)觀察者,接收到的值為");
  console.log(value)
});
fs.readFile("h.js", (err, data) => {
  observer.notifyAll(data.toString())
});

發(fā)布-訂閱模式


// 發(fā)布-訂閱
const event = new EventEmitter();
event.on("err", console.log);
event.on("data", data => {
  // do something
  console.log(data)
});
fs.readFile("h.js", (err, data) => {
  if (err) event.emit("err", err);
  event.emit("data", data.toString())
});

兩種設(shè)計(jì)模式在異步編程中,都是通過注冊(cè)全局觀察者或全局事件,然后在異步環(huán)境里通知所有觀察者或觸發(fā)特定事件來實(shí)現(xiàn)異步編程。
劣勢(shì)也很明顯,比如全局觀察者/事件過多難以維護(hù),事件名命沖突等等,因此Promise便誕生了,Promise在一定程度上繼承了觀察者和發(fā)布-訂閱設(shè)計(jì)模式的思想。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容