1.引言
通過手寫符合A+規范的promise,來深入了解Promise,再結合相關面試題,爭取做到在面試的時候,如果問Promise,咱們能全方位吊打面試官😁😁😁
下面的每一個寫法都對應Promise的一些特性,不斷升級,了解原理后再做題就會發現很簡單了
2.極簡版promise
2.1 基礎特性
詳細介紹的話大家去看 阮一峰es6-promise,我這里當你已經有一定的基礎了,然后我們總結一下基本特性
new Promise((resolve,reject)=>{ //excutor
setTiemout(()=>{
resolve(1) //resolve中的值會傳遞到成功的回呼函式引數中
},1000)
}).then((val)=>{ //onFulfiled
console.log(val)
},(e)=>{ //onRejected
console.log(e)
})
- Promise物件初始狀態值為
pending - 立即執行excutor,在excutor中可以通過resolve,reject方法改變promise狀態,分別改為filfiled(成功)和rejected(失敗)
- 狀態一旦改變狀態就凝固了,無法再變
- then方法中的回呼函式會在狀態改變后執行,成功調成功回呼,失敗呼叫失敗回呼
- resolve中的值會傳遞到成功的回呼函式引數中 (失敗類似)
2.2實作
思路:上述功能點1、2、3、5都比較好實作,4的話采用發布訂閱模式也能實作
class Promise {
constructor(executor) {
this.status='pending' //三狀態
this.value = undefined //引數
this.reason = undefined
this.onFulfilled = [] //發布訂閱中儲存回呼
this.onRejected = []
let resolve = (value)=>{
if(this.status==='pending'){
this.status = 'fulfilled'
this.value = value
this.onFulfilled.forEach(fn=>fn(this.value)) //發布訂閱模式,異步一改變狀態則立即執行回呼
}
}
let reject = (reason)=>{
if(this.status==='pending'){
this.status = 'rejected'
this.reason = reason
this.onRejected.forEach(fn=>fn(this.reason))
}
}
try{
executor(resolve,reject) //executor同步執行
}catch (e) {
reject(e)
}
}
then(onFulfilled, onRejected) { // 如果then的時候 根據當前狀態執行成功或者失敗
if(this.status==='fulfilled'){
onFulfilled(this.value)
}
if(this.status==='rejected'){
onRejected(this.reason)
}
if(this.status==='pending'){
this.onFulfilled.push(onFulfilled) //發布訂閱模式儲存異步回呼
this.onRejected.push(onRejected)
}
}
}
3.添加鏈式呼叫
3.1 鏈式特性
1.如果promise中的then方法,無論是成功還是失敗,他的回傳結果是一個普通的時候就會把這個結果傳遞給外層的then的下一個then的成功回呼
Promise.reject().then((val)=>{
return 'ok'
},()=>{
return 'err'
}).then((val)=>{
console.log('ok' + val)
},(e)=>{
console.log('err' + e)
})
// okerr 第一個then失敗的回呼回傳的是普通值,還是走第二個的then中成功回呼
2.如果成功或者失敗的回呼的回傳值 回傳是一個promise 那么會讓這個promise執行 采用他的狀態
Promise.resolve().then(()=>{
return new Promise((resolve)=>{
setTimeout(()=>{
resolve(1)
},1000)
})
}).then((val)=>{
console.log(val)
})
//一秒后列印1
3.2實作
這一版主要是實作鏈式呼叫,稍微繞一點,但是理清楚了也不難
首先明確一下,then后面會回傳一個新的Promise,所以才能執行鏈式呼叫
第一個比較繞的地方,怎么讓第二個then里面的回呼執行?只要呼叫then回傳的新promise(promise2)時的resolve方法就行了
第二個比較繞的地方就是引數是什么?我們看特性3.1,引數是什么要根據第一個then中回呼的回傳值來判斷,回傳值如果是正常值,如果是Piomise,,所以我們封裝一個resolvePromise的方法來處理,引數的話有第一個then的回呼,新創建的promise2,以及promise2里面的resolve.reject
需要改變的核心代碼如下
let resolvePromise = (promise2, x, resolve, reject) => {...}
class Promise {
construcotr(){...}
then(){
let promise2 = new promise((resolve,reject)=>{
let x = onFulfiled() // onFulfilef是第一個then中的回呼函式
resolvePromise(promise2,x, resolve, reject)
})
return promiese2
}
}
resolvePromise這個方法會判斷onFulfiled回傳值型別,如果是普通值會怎么樣,如果是一個Promise會怎么樣,如果報錯會怎么樣,詳細實作方法可以參考promise A+規范
完整實作
let resolvePromise = (promise2, x, resolve, reject) => {
// 監測到環形鏈
if(promise2===x) return new TypeError('chaining cycle detected for promise')
if(typeof x ==='function' ||(typeof x ==='object' && x!==null)){
try{
//嘗試取出then,有問題報錯
let then = x.then
if(typeof then === 'function'){ //這里是最繞的,想清楚promise2和x的關系,x.then會不會執行取決于使用者的邏輯,會不會在第一個then中回呼函式中回傳的promise中呼叫它的resolve改變狀態
then.call(x,resolve,reject)
}else{// then不是function
resolve(x)
}
}catch (e) {
reject(e)
}
}else{ //普通型別
resolve(x)
}
}
class Promise {
constructor(executor) {
this.status = 'pending'
this.value = undefined
this.reason = undefined
this.onFulfilledCallback = []
this.onRejectedCallback = []
let resolve = (value) => {
if (this.status === 'pending') {
this.status = 'fulfilled'
this.value = value
this.onFulfilledCallback.forEach(fn => fn(this.value))
}
}
let reject = (reason) => {
if (this.status === 'pending') {
this.status = 'rejected'
this.reason = reason
this.onRejectedCallback.forEach(fn => fn(this.reason))
}
}
try {
executor(resolve, reject)
} catch (e) {
reject(e)
}
}
then(onFulfilled, onRejected) { // 如果then的時候 根據當前狀態執行成功或者失敗
let promise2 = new Promise((resolve, reject) => {
if (this.status === 'fulfilled') {
setTimeout(() => { //這里之所以異步是因為必須保證resolvePromise(promise2, x, resolve, reject)時Promise2創建完成
try {
let x = onFulfilled(this.value)
resolvePromise(promise2, x, resolve, reject)
} catch (e) {
reject(e)
}
})
}
if (this.status === 'rejected') {
setTimeout(() => {
try {
let x = onRejected(this.reason)
resolvePromise(promise2, x, resolve, reject)
} catch (e) {
reject(e)
}
})
}
if (this.status === 'pending') {
this.onFulfilledCallback.push(() => {
setTimeout(() => {
try {
let x = onFulfilled(this.value)
resolvePromise(promise2, x, resolve, reject)
} catch (e) {
reject(e)
}
})
})
this.onRejectedCallback.push(() => {
setTimeout(() => {
try {
let x = onRejected(this.reason)
resolvePromise(promise2, x, resolve, reject)
} catch (e) {
reject(e)
}
})
})
}
})
return promise2
}
}
基本面試5-10分鐘代碼寫到這里,都能給滿分通過,剩下的就是4個打補丁的地方了
4.打補丁
4.1 補丁點
實際上是A+規范測驗用例的補丁,我按重要程度往下排,前面的必須做到能寫出來(面試可以不寫),后面的知道即可
- then的默認引數配置
- x可能是個Promise,它的回傳值還可能是個Pormise,這個Promised的回傳值還可能是個Promise…
- 呼叫Promise的resolve方法,如果引數是個promise怎么辦 (這個不在A+規范里,但是新版promise實作了)
- 別人實作的可能不規范,我們的resolvePromise需要加一點限制,改變了狀態就不能再變了 (這個在A+規范測驗用例里,但是我感覺意義不大)
4.1.1 默認引數
Promise.resolve(1).then().then().then().then((val)=>{
console.log(val) //1
})
//失敗也是類似的傳遞
可以默認傳遞一個回呼函式
then(onFufilled,onRejected){
onFufilled = typeof onFufilled === 'function'?onFufilled:value=>value;
...
}
4.1.2 x中promise嵌套
這個也不難,遞回呼叫resolvePromise去決議
let resolvePromise = (promise2,x,resolve,reject) => {
...
then = x.then
/*這個是之前的核心代碼 then.call(x,resolve,reject)
*實際等同于 then.call(x,(y)=>{
* resolve(y) 這個y是x作為promise的回傳值,現在這個y可能是個promise所以再遞回呼叫resolvePromise去決議
* },reject)
*/
改成這樣:
then.call(x,(y)=>{
resolvePromise((promise2,y,resolve,reject)
},reject)
...
}
4.1.3 resolve中是promise
constructor(executor){
...
let resolve = (value) =>{ // 如果resolve的值時一個promise
if(value instanceof Promise){
// 我就讓這個promise執行,把成功的結果再次判斷
return value.then(resolve,reject) //引數會自動傳遞到resolve里面去
}
}
...
5.添加方法
Promise比較重要的方法一共有五個方法
5.1 Promise.resovle
把一個物件包裝成Promise物件,特別注意狀態不一定是成功的
各種注意事項請看阮一峰es6-promise
直接記憶不好記憶,但是結合原始碼很簡單,理所當然
static resolve(value){
return new Promise((resolve,reject)=>{
resolve(value);
})
}
5.2 Promise.reject
Promise.reject(reason)方法也會回傳一個新的 Promise 實體,該實體的狀態為rejected,
static reject(err){
return new Promise((resolve,reject)=>{
reject(err);
})
}
5.3 PromiseInstance.prototype.finally
這個是實體方法,其他幾個都是類方法
無論成功還是失敗都會呼叫,所以可定回傳的也是一個Promimse,成功失敗都會呼叫傳入的回呼,
finally不接受值,回傳的Promise的狀態受前一個promise狀態的影響
finally如果在中間同時回呼回傳一個promise則會等待promise
Promise.resolve(1).finally(
(a)=>{
return new Promise((resolve)=>{
setTimeout(function () {
resolve(2)
},3000)
})
}
).then((data)=>{
console.log(data)
})
等待3秒后列印1
finally實作
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 })
);
};
5.4 Promise.race Promise.all
race和all一個是誰先呼叫誰執行后面then中的回呼,一個是全部呼叫才執行后面then中的回呼
他們都需要對引數中傳入的陣列進行遍歷
all的實作需要借助計數器,這也是實作異步任務通知的一種方法
直接完成或者異步完成都會使計數器加1 當計數器和陣列長度相等時就是all方法完成的時候,然后把結果陣列傳到下一個回呼
race的實作就是,遍歷陣列中元素current,都去改變回傳promise的值,誰先改變就取誰的值傳到會帶到函式里面
return promose((resolve,reject)=>{
if(isPromise(current)){
current.then(resolve,reject)
}else{
resolve(current)
}
})
具體實作見6
6.完整實作
let resolvePromise = (promise2,x,resolve,reject) => {
if(promise2 === x){
return reject(new TypeError('Chaining cycle detected for promise #<Promise>'))
}
// 如果呼叫了失敗 就不能再呼叫成功 呼叫成功也不能再呼叫失敗
let called;
if(typeof x ==='function' || (typeof x === 'object' && x!== null) ){
try{
let then = x.then; // Object,dedefineProperty
if(typeof then === 'function'){
then.call(x,(y)=>{ // x.then(y=>,err=>)
if(called) return;
called = true
// y有可能決議出來的還是一個promise
// 在去呼叫resolvePromise方法 遞回決議的程序
// resolve(y)
resolvePromise(promise2,y,resolve,reject); // 總有y是普通值的時候
},e=>{
if(called) return;
called = true
reject(e);
})
}else{
if(called) return;
called = true
resolve(x);
}
}catch(e){
if(called) return;
called = true
reject(e);
}
}else{
if(called) return;
called = true
resolve(x); // '123' 123
}
}
class Promise{
constructor(executor){
this.value = undefined;
this.reason = undefined;
this.status = 'pending';
this.onResolvedCallbacks = [];
this.onRejectedCallbacks = [];
let resolve = (value) =>{ // 如果resolve的值時一個promise
// if(typeof value === 'function' || (typeof value == 'object'&&value !== null)){
// if(typeof value.then == 'function'){
// return value.then(resolve,reject)
// }
// }
if(value instanceof Promise){
// 我就讓這個promise執行,把成功的結果再次判斷
return value.then(resolve,reject) //引數會自動傳遞到resolve里面去
}
if(this.status === 'pending'){
this.status = 'fulfilled'
this.value = value;
this.onResolvedCallbacks.forEach(fn=>fn());
}
}
let reject = (reason) =>{
if(this.status === 'pending'){
this.status = 'rejected'
this.reason = reason;
this.onRejectedCallbacks.forEach(fn=>fn());
}
}
try{
executor(resolve,reject);
}catch(e){
console.log(e)
reject(e);
}
}
then(onFufilled,onRejected){
// 可選引數的配置
onFufilled = typeof onFufilled === 'function'?onFufilled:value=>value;
onRejected = typeof onRejected === 'function'?onRejected:err=>{throw err}
let promise2 = new Promise((resolve,reject)=>{
if(this.status === 'fulfilled'){
setTimeout(()=>{ // 為了保證promise2 已經產生了
try{
let x = onFufilled(this.value);
resolvePromise(promise2,x,resolve,reject);
}catch(e){
console.log(e);
reject(e);
}
})
}
if(this.status === 'rejected'){
setTimeout(() => {
try{
let x= onRejected(this.reason);
resolvePromise(promise2,x,resolve,reject);
}catch(e){
reject(e);
}
});
}
if(this.status === 'pending'){
this.onResolvedCallbacks.push(()=>{
setTimeout(() => {
try{
let x = onFufilled(this.value);
resolvePromise(promise2,x,resolve,reject);
}catch(e){
reject(e);
}
})
});
this.onRejectedCallbacks.push(()=>{
setTimeout(() => {
try{
let x= onRejected(this.reason);
resolvePromise(promise2,x,resolve,reject);
}catch(e){
reject(e);
}
});
});
}
})
return promise2
}
finally(callback){
let P = this.constructor;
return this.then(
value => P.resolve(callback()).then(() => value),
reason => P.resolve(callback()).then(() => { throw reason })
);
}
catch(errCallback){ // catch是then的一個別名而已
return this.then(null,errCallback)
}
static resolve(value){
return new Promise((resolve,reject)=>{
resolve(value);
})
}
static reject(err){
return new Promise((resolve,reject)=>{
reject(err);
})
}
static race(values){
return new Promise((resolve,reject)=>{
for(let i = 0 ; i<values.length;i++){
let current = values[i];
if(isPromise(current)){
current.then(resolve,reject)
}else{
resolve(current)
}
}
})
}
static all(values){
return new Promise((resolve,reject)=>{
let arr = []; // 最終的結果
let i = 0;
function processData(key,val) {
arr[key] = val;
if(++i == values.length){
resolve(arr);
}
}
for(let i = 0 ; i<values.length;i++){
let current = values[i];
if(isPromise(current)){
current.then(y=>{
processData(i,y);
},reject)
}else{
processData(i,current);
}
}
})
}
}
Promise.deferred = () => { // 測驗方法
let dfd = {};
dfd.promise = new Promise((resolve,reject)=>{
dfd.resolve = resolve;
dfd.reject = reject;
})
return dfd; // 可以檢測這個物件上的promise屬性 resolve方法 reject方法
}
module.exports = Promise;
// 全域安裝 只能在命令中使用 sudo npm install promises-aplus-tests -g
// promises-aplus-tests promise.js
// 本地安裝 可以在命令下 和 我們的代碼中使用
7.面試題
7.1 請寫出下面代碼運行結果
Promise.reject(1).then().finally(
(a)=>{
console.log('a:'a) //undefined
setTimeout(function () {
console.log(2)
},3000)
}
).then((data)=>{
console.log(3)
console.log(data)
},(e)=>{
console.log('error'+e) //列印error1
})
//
答案:a:undefined error1 過兩秒 2
7.2 promise構造器是同步還是異步,then方法呢
來源:微醫
答案:同步,異步 原始碼里面寫的很清楚
7.3 模擬實作一個Promise.finally
答案:
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 })
);
};
7.4 介紹一下Promose.all的使用,原理及錯誤處理
使用:需要同時獲取多個東西后再執行回呼
原理:回傳一個Promise: p 遍歷引數陣列,若不是promise,直接加入到結果陣列arr中 計數器++
如果是Promise,等Promise執行完再講結果加到加過陣列 計數器++
計數器===陣列長度時證明全部完成,p.resolve(結果陣列arr)
錯誤處理: p.reject(e)
7.5 設計并實作Promise.race
答案:
Promise._race = promises => new Promise((resolve, reject) => {
promises.forEach(promise => {
promise.then(resolve, reject)
})
})
8.總結
總結了Promise的實作,以及面試常見考點,相信如果全部理解了,面試再問promise肯定可以加分不少,由于技術有限,如果閱讀中發現有什么錯誤,請在留言指出,
小編開了個逐點突破系列,一篇文章來講一個知識點,學習要系統,知識點也需要歸納總結,文章還會包括常見的相關面試題,當然這個系列文章篇幅會比較長,大家可以收藏慢慢看,有什么建議或可以優化的也歡迎大家提出來,逐點突破系列還請大家多多支持啦!
9.最后
文章中出現的面試題還沒有看過癮的可以【點擊這里】免費獲取完整版前端面試題決議PDF哦!


如果你覺得本文對你有很大的幫助,喜歡這個系列,請評論點贊轉發來告訴我哦!你們的支持是我最大動力!
轉載請註明出處,本文鏈接:https://www.uj5u.com/qianduan/275812.html
標籤:其他
上一篇:九、HTML5 新增
