Promise 面試題整理


基礎輸出題

  • 題目1
const promise = new Promise((resolve, reject) => {
    console.log(1)
    resolve()
    console.log(2)
})
promise.then(() => {
    console.log(3)
})
console.log(4)

記住 new Promise 里的參數函數,是同步被執行的,故而先輸出 1,2.

resolve 后還需要等待進入下一個事件循環。then 把參數函數推入微任務隊列,並不直接執行。

輸出 4,接着事件循環進入下一輪,輸出 3.

1
2
4
3
  • 題目2

來自網易。給出一個 promise

var promise = new Promise(function(resolve, reject){
  setTimeout(function() {
    resolve(1);
  }, 3000)
})

請問這三種有何不同?

// 1
promise.then(() => {
  return Promise.resolve(2);
}).then((n) => {
  console.log(n)
});

// 2
promise.then(() => {
  return 2
}).then((n) => {
  console.log(n)
});

// 3
promise.then(2).then((n) => {
  console.log(n)
});
  1. 輸出2。Promise.resolve 就是一個 Promise 對象就相當於返回了一個新的 Promise 對象。然后在下一個事件循環里才會去執行 then
  2. 輸出2。和上一點不一樣的是,它不用等下一個事件循環。
  3. 輸出1。then 和 catch 期望接收函數做參數,如果非函數就會發生 Promise 穿透現象,打印的是上一個 Promise 的返回。
  • 題3

來自快手的變態題目,好討厭。得配合事件循環機制來看

let a;
const b = new Promise((resolve, reject) => {
  console.log('promise1');
  resolve();
}).then(() => {
  console.log('promise2');
}).then(() => {
  console.log('promise3');
}).then(() => {
  console.log('promise4');
});

a = new Promise(async (resolve, reject) => {
  console.log(a);
  await b;
  console.log(a);
  console.log('after1');
  await a
  resolve(true);
  console.log('after2');
});

console.log('end');

我直到現在也沒有想明白其中的一步,先貼答案

promise1
undefined
end
promise2
promise3
promise4
Promise { pending }
after1

第一個輸出 promise1,是因為 Promise 里的方法立即執行。接着調用 resolve,只不過 then 里的方法等下一個周期

第二個輸出 undefined,是因為立即執行執行 a 內部的方法,先 console.log(a),但此時的 a 還沒賦值給左邊的變量,所以只能是 undefined。然后 await b 就得等下一個周期執行了。

第三個輸出 end,自然不意外。

接着輸出 promise2,promise3,promise4,是因為 await b 等待他執行完了,才輪到 a 內部繼續執行。

輸出 Promise { pending },腦筋轉了以下才想通,事件都進入了循環了,a 肯定已經被賦值成了 Promise 對象。所以第二遍 console.log(a),自然就輸出這個了。

輸出 after1 不奇怪。

但是隨后的 await a 是個什么奇怪的操作,想半天沒搞懂為何最后不輸出 after2,調試得知根本就執行不到 await a 以后的代碼上,想不懂。

更新:和不少朋友交流后,我得出了結論,await a 時,a 是必須等待 Promise 的狀態從 pending 到 fullfilled 才會繼續往下執行,可 a 的狀態是一直得不到更改的,所以無法執行下面的邏輯。只要在 await a 上面加一行 resolve() 就能讓后面的 after 2 得到輸出。

  • 題4
const promise = new Promise((resolve, reject) => {
  resolve('success1');
  reject('error');
  resolve('success2');
});

promise
  .then((res) => {
    console.log('then: ', res);
  })
  .catch((err) => {
    console.log('catch: ', err);
  });

簡單題,牢牢記住 Promise 對象的狀態只能被轉移一次,resolve('success1') 時狀態轉移到了 fullfilled 。后面 reject 就調用無效了,因為狀態已經不是 pending。

  • 題5
Promise.resolve()
  .then(() => {
    return new Error('error!!!')
  })
  .then((res) => {
    console.log('then: ', res)
  })
  .catch((err) => {
    console.log('catch: ', err)
  })

沒有拋出錯誤和異常,只是 return 了一個對象,哪怕他是 Error 對象,那自然也是正常走 then 的鏈式調用下去了,不會觸發 catch。

手寫 Promise

曾經實現過,掛在博客上JS 來實現一個 Promise

簡述:內部有個 state,doneList,failList,方法 resolve,reject,返回 fn(resolve, reject)。原型鏈有個 then,里面做的事情就是把函數參數推入 doneList 和 failList。然后 resolve 里執行的邏輯就是拿出 doneList 順序執行下去,reject 執行 failList。

怎么取消掉 Promise

給你一個 promise,但是希望取消掉它。

let promise = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve(123);
  }, 1000);
});

axios 倒是有 abort 方法,但面試里你肯定不得用。

promise 其實缺陷就是無法得知執行到了哪兒,也無法取消,只能被動的等 resolve 或者 reject 執行或者拋錯。所以思路就是外部包裹一層 Promise,並對外提供 abort 方法,這個 abort 方法可以用來 reject 內部的 Promise 對象。

function wrap(p) {
  let resol = null;
  let abort = null;

  let p1 = new Promise((resolve, reject) => {
    resol = resolve;
    abort = reject;
  });

  p1.abort = abort;
  p.then(resol, abort);

  return p1;
}

let newPromise = wrap(promise);

newPromise.then(res => console.log)
newPromise.abort()

順序輸出

不使用 async/await,給你若干個 promise 對象,你怎么保證它是順序執行的?

var makePromise = function(value, time) {
  return new Promise(function(resolve, reject){
    setTimeout(function() {
      resolve(value);
    }, time)
  })
};

function order(promises) {
}

order([
  makePromise('a', 3000),
  makePromise('b', 5000),
  makePromise('c', 2000),
]);

思路是 then 里返回下一個

function order(promises) {
  let dataArr = []
  let promise = Promise.resolve();
  for (let i = 0; i < promises.length; i++) {
    promise = promise.then((data) => {
      if (data) {
        dataArr.push(data);
        console.log(data);
      }
      return promises[i];
    });
  }
  return promise.then(data => {
    console.log(data);
  })
}

其實這么做有個問題,數組里的 promise 三項其實還是並發的,只不過輸出的順序的確符合期望,但是間隔的時間會比較奇怪,3s 后輸出 a,再間隔 2s 連着輸出了 b 和 c。

要想讓間隔時間也完全符合串行的話,那么還是在 then 里制造下一個 Promise 對象並返回吧。

並發做異步請求,限制頻率

舉個例子,有 8 張圖片 url,你需要並發去獲取它,並且任何時刻同時請求的數量不超過 3 個。也就是說第 4 張圖片一定是等前面那一批有一個請求完畢了才能開始,以此類推。

var urls = [
  'https://www.kkkk1000.com/images/getImgData/getImgDatadata.jpg',
  'https://www.kkkk1000.com/images/getImgData/gray.gif',
  'https://www.kkkk1000.com/images/getImgData/Particle.gif',
  'https://www.kkkk1000.com/images/getImgData/arithmetic.png',
  'https://www.kkkk1000.com/images/getImgData/arithmetic2.gif',
  'https://www.kkkk1000.com/images/getImgData/getImgDataError.jpg',
  'https://www.kkkk1000.com/images/getImgData/arithmetic.gif',
  'https://www.kkkk1000.com/images/wxQrCode2.png',
];

function loadImg(url) {
  return new Promise((resolve, reject) => {
    const img = new Image();
    img.onload = () => {
      console.log('一張圖片加載完成', url);
      resolve();
    };
    img.onerror = reject;
    img.src = url;
  });
}

function limitload(urls, limit) {
  
}


limitload(urls, 2);

解法就是前 3 個放入一個外部的 promise 里同時進行,then 方法指定去請求后面的圖片。

function limitload(urls, limit) {
  let index = limit;
  function execNewPromise() {
    index += 1;
    if (index < urls.length) {
      return loadImg(urls[index]).then(() => execNewPromise());
    }
    
  }
  var promise = Promise.resolve();
  promise.then(() => {
    for (let i = 0; i < limit; i++) {
      loadImg(urls[i]).then(() => execNewPromise());  
    }
  });
  
}

總結

單單能說事件循環機制本身還是不夠,總有面試問題會非常惡心人,還是得多做幾道備用,這個體驗挺糟糕的,比讓我手寫 leetcode 算法題還難受。畢竟誰的代碼會寫成那樣呢。

要我手寫 Promise 的公司,近兩個月里出現了兩家。第一次被問到時,雖然很久以前我寫過並放入博客中,但真的上手寫時還是很容易卡殼的,大家小心。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM