亚洲中字慕日产2020,大陆极品少妇内射AAAAAA,无码av大香线蕉伊人久久,久久精品国产亚洲av麻豆网站

資訊專欄INFORMATION COLUMN

從0到1實(shí)現(xiàn)Promise

EddieChan / 3561人閱讀

摘要:通過或者拿到方法回調(diào)函數(shù)的返回值,然后調(diào)用,將新增的的和傳入到中。打印結(jié)果實(shí)現(xiàn)方法接收一個(gè)包含多個(gè)的數(shù)組,當(dāng)有一個(gè)為狀態(tài)時(shí),整個(gè)大的為,并執(zhí)行回調(diào)函數(shù)。

前言

Promise大家一定都不陌生了,JavaScript異步流程從最初的Callback,到Promise,到Generator,再到目前使用最多的Async/Await(如果對(duì)于這些不熟悉的可以參考我另一篇文章《JavaScript異步編程》),這不僅僅是技術(shù)實(shí)現(xiàn)的發(fā)展,更是思想上對(duì)于如何控制異步的遞進(jìn)。Promise作為后續(xù)方案的基礎(chǔ),是重中之重,也是面試時(shí)候最常被問到的。

今天我們就一起從0到1實(shí)現(xiàn)一個(gè)基于A+規(guī)范的Promise,過程中也會(huì)對(duì)Promise的異常處理,以及是否可手動(dòng)終止做一些討論,最后會(huì)對(duì)我們實(shí)現(xiàn)的Promise做單元測(cè)試。完整的代碼已經(jīng)上傳到github,想直接看代碼的可以點(diǎn)這里。

雖然已經(jīng)有很多帶你實(shí)現(xiàn)Promise類的文章了,但每個(gè)人理解的程度不一樣,也許不同的文章可以帶給你不同的思考呢,那我們就開始吧。

正文 1. 基礎(chǔ)框架

new Promise()時(shí)接收一個(gè)executor函數(shù)作為參數(shù),該函數(shù)會(huì)立即執(zhí)行,函數(shù)中有兩個(gè)參數(shù),它們也是函數(shù),分別是resolve和reject,函數(shù)同步執(zhí)行一定要放在try...catch中,否則無法進(jìn)行錯(cuò)誤捕獲。

MyPromise.js

function MyPromise(executor) {

  function resolve(value) {

  }

  function reject(reason) {
    
  }

  try {
    executor(resolve, reject);
  } catch (reason) {
    reject(reason);
  }
}

module.exports = MyPromise;

resolve()接收Promise成功值value,reject接收Promise失敗原因reason。

test.js

let MyPromise = require("./MyPromise.js");

let promise = new MyPromise(function(resolve, reject) {
  resolve(123);
})
2. 添加狀態(tài)機(jī)

目前實(shí)現(xiàn)存在的問題:

Promise是一個(gè)狀態(tài)機(jī)的機(jī)制,初始狀態(tài)為 pending,成功狀態(tài)為 fulfilled,失敗狀態(tài)為 rejected。只能從 pending -> fulfilled,或者從 pending -> rejected,并且狀態(tài)一旦轉(zhuǎn)變,就永遠(yuǎn)不會(huì)再變了。

所以,我們需要為Promise添加一個(gè)狀態(tài)流轉(zhuǎn)的機(jī)制。

MyPromise.js

const PENDING = "pending";
const FULFILLED = "fulfilled";
const REJECTED = "rejected";

function MyPromise(executor) {
  let self = this;
  self.state = PENDING;


  function resolve(value) {
    if (self.state === PENDING) {
      self.state = FULFILLED;
    }
  }

  function reject(reason) {
    if (self.state === PENDING) {
      self.state = REJECTED;
    }
  }

  try {
    executor(resolve, reject);
  } catch (reason) {
    reject(reason);
  }
}

module.exports = MyPromise;

test.js

let MyPromise = require("./MyPromise.js");

let promise = new MyPromise(function(resolve, reject) {
  resolve(123);
});

promise.then(function(value) {
  console.log("value", value);
}, function(reason) {
  console.log("reason", reason);
})
3. 添加then方法

Promise擁有一個(gè)then方法,接收兩個(gè)函數(shù) onFulfilledonRejected,分別作為Promise成功和失敗的回調(diào)。所以,在then方法中我們需要對(duì)狀態(tài)state進(jìn)行判斷,如果是fulfilled,則執(zhí)行onFulfilled(value)方法,如果是rejected,則執(zhí)行onRejected(reason)方法。

由于成功值value和失敗原因reason是由用戶在executor中通過resolve(value)reject(reason)傳入的,所以我們需要有一個(gè)全局的valuereason供后續(xù)方法獲取。

MyPromise.js

const PENDING = "pending";
const FULFILLED = "fulfilled";
const REJECTED = "rejected";

function MyPromise(executor) {
  let self = this;

  self.state = PENDING;
  self.value = null;
  self.reason = null;

  function resolve(value) {
    if (self.state === PENDING) {
      self.state = FULFILLED;
      self.value = value;
    }
  }

  function reject(reason) {
    if (self.state === PENDING) {
      self.state = REJECTED;
      self.reason = reason;
    }
  }

  try {
    executor(resolve, reject);
  } catch (reason) {
    reject(reason);
  }
}

MyPromise.prototype.then = function(onFuifilled, onRejected) {
  let self = this;

  if (self.state === FULFILLED) {
    onFuifilled(self.value);
  }

  if (self.state === REJECTED) {
    onRejected(self.reason);
  }
};

module.exports = MyPromise;
4. 實(shí)現(xiàn)異步調(diào)用resolve

目前實(shí)現(xiàn)存在的問題:

同步調(diào)用resolve()沒有問題,但如果是異步調(diào)用,比如放到setTimeout中,因?yàn)槟壳暗拇a在調(diào)用then()方法時(shí),state仍是pending狀態(tài),當(dāng)timer到時(shí)候調(diào)用resolve()state修改為fulfilled狀態(tài),但是onFulfilled()函數(shù)已經(jīng)沒有時(shí)機(jī)調(diào)用了。

針對(duì)上述問題,進(jìn)行如下修改:

MyPromise.js

const PENDING = "pending";
const FULFILLED = "fulfilled";
const REJECTED = "rejected";

function MyPromise(executor) {
  let self = this;

  self.state = PENDING;
  self.value = null;
  self.reason = null;
  self.onFulfilledCallbacks = [];
  self.onRejectedCallbacks = [];

  function resolve(value) {
    if (self.state === PENDING) {
      self.state = FULFILLED;
      self.value = value;

      self.onFulfilledCallbacks.forEach(function(fulfilledCallback) {
        fulfilledCallback();
      });
    }
  }

  function reject(reason) {
    if (self.state === PENDING) {
      self.state = REJECTED;
      self.reason = reason;

      self.onRejectedCallbacks.forEach(function(rejectedCallback) {
        rejectedCallback();
      });
    }
  }

  try {
    executor(resolve, reject);
  } catch (reason) {
    reject(reason);
  }
}

MyPromise.prototype.then = function(onFuifilled, onRejected) {
  let self = this;

  if (self.state === PENDING) {
    self.onFulfilledCallbacks.push(() => {
        onFuifilled(self.value);
    });
    self.onRejectedCallbacks.push(() => {
        onRejected(self.reason);
    });
  }

  if (self.state === FULFILLED) {
    onFuifilled(self.value);
  }

  if (self.state === REJECTED) {
    onRejected(self.reason);
  }
};

module.exports = MyPromise;

我們添加了兩個(gè)回調(diào)函數(shù)數(shù)組onFulfilledCallbacksonRejectedCallbacks,用來存儲(chǔ)then()方法中傳入的成功和失敗回調(diào)。然后,當(dāng)用戶調(diào)用resolve()reject()的時(shí)候,修改state狀態(tài),并從相應(yīng)的回調(diào)數(shù)組中依次取出回調(diào)函數(shù)執(zhí)行。

同時(shí),通過這種方式我們也實(shí)現(xiàn)了可以注冊(cè)多個(gè)then()函數(shù),并且在成功或者失敗時(shí)按照注冊(cè)順序依次執(zhí)行。

test.js

let MyPromise = require("./MyPromise.js");

let promise = new MyPromise(function(resolve, reject) {
  setTimeout(function() {
    resolve(123);
  }, 1000);
});

promise.then(function(value) {
  console.log("value1", value);
}, function(reason) {
  console.log("reason1", reason);
});

promise.then(function(value) {
  console.log("value2", value);
}, function(reason) {
  console.log("reason2", reason);
});
5. then返回的仍是Promise

讀過PromiseA+規(guī)范的同學(xué)肯定知道,then()方法返回的仍是一個(gè)Promise,并且返回Promise的resolve的值是上一個(gè)Promise的onFulfilled()函數(shù)或onRejected()函數(shù)的返回值。如果在上一個(gè)Promise的then()方法回調(diào)函數(shù)的執(zhí)行過程中發(fā)生了錯(cuò)誤,那么會(huì)將其捕獲到,并作為返回的Promise的onRejected函數(shù)的參數(shù)傳入。比如:

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

promise.then((value) => {
  console.log("value1", value);
  return 456;
}).then((value) => {
  console.log("value2", value);
});

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

打印結(jié)果為:

value1 123   
value2 456
let promise = new Promise((resolve, reject) => {
  resolve(123);
});

promise.then((value) => {
  console.log("value1", value);
  a.b = 2;    // 這里存在語法錯(cuò)誤
  return 456;
}).then((value) => {
  console.log("value2", value);
}, (reason) => {
  console.log("reason2", reason);
});

打印結(jié)果為:

value1 123   
reason2 ReferenceError: a is not defined

可以看到,then()方法回調(diào)函數(shù)如果發(fā)生錯(cuò)誤,會(huì)被捕獲到,那么then()返回的Promise會(huì)自動(dòng)變?yōu)?b>onRejected,執(zhí)行onRejected()回調(diào)函數(shù)。

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

promise.then((value) => {
  console.log("value1", value);
  return 456;
}, (reason) => {
  console.log("reason1", reason);
  return 456;
}).then((value) => {
  console.log("value2", value);
}, (reason) => {
  console.log("reason2", reason);
});

打印結(jié)果為:

reason1 123   
value2 456

好啦,接下來我們就去實(shí)現(xiàn)then()方法依然返回一個(gè)Promise。

MyPromise.js

MyPromise.prototype.then = function(onFuifilled, onRejected) {
  let self = this;
  let promise2 = null;

  promise2 = new MyPromise((resolve, reject) => {
    if (self.state === PENDING) {
      self.onFulfilledCallbacks.push(() => {
        try {
          let x = onFuifilled(self.value);
          self.resolvePromise(promise2, x, resolve, reject);
        } catch(reason) {
          reject(reason);
        }
      });
      self.onRejectedCallbacks.push(() => {
        try {
          let x = onRejected(self.reason);
          self.resolvePromise(promise2, x, resolve, reject);
        } catch(reason) {
          reject(reason);
        }
      });
    }
  
    if (self.state === FULFILLED) {
      try {
        let x = onFuifilled(self.value);
        self.resolvePromise(promise2, x, resolve, reject);
      } catch (reason) {
        reject(reason);
      }
    }
  
    if (self.state === REJECTED) {
      try {
        let x = onRejected(self.reason);
        self.resolvePromise(promise2, x, resolve, reject);
      } catch (reason) {
        reject(reason);
      }
    }
  });

  return promise2;
};

可以看到,我們新增了一個(gè)promise2作為then()方法的返回值。通過let x = onFuifilled(self.value) 或者 let x = onRejected(self.reason)拿到then()方法回調(diào)函數(shù)的返回值,然后調(diào)用self.resolvePromise(promise2, x, resolve, reject),將新增的promise2、xpromise2resolvereject傳入到resolvePromise()中。

所以,下面我們重點(diǎn)看一下resolvePromise()方法。

MyPromise.js

MyPromise.prototype.resolvePromise = function(promise2, x, resolve, reject) {
  let self = this;
  let called = false;   // called 防止多次調(diào)用

  if (promise2 === x) {
    return reject(new TypeError("循環(huán)引用"));
  }

  if (x !== null && (Object.prototype.toString.call(x) === "[object Object]" || Object.prototype.toString.call(x) === "[object Function]")) {
    // x是對(duì)象或者函數(shù)
    try {
      let then = x.then;

      if (typeof then === "function") {
        then.call(x, (y) => {
          // 別人的Promise的then方法可能設(shè)置了getter等,使用called防止多次調(diào)用then方法
          if (called) return ;
          called = true;
          // 成功值y有可能還是promise或者是具有then方法等,再次resolvePromise,直到成功值為基本類型或者非thenable
          self.resolvePromise(promise2, y, resolve, reject);
        }, (reason) => {
          if (called) return ;
          called = true;
          reject(reason);
        });
      } else {
        if (called) return ;
        called = true;
        resolve(x);
      }
    } catch (reason) {
      if (called) return ;
      called = true;
      reject(reason);
    }
  } else {
    // x是普通值,直接resolve
    resolve(x);
  }
};

resolvePromise()是用來解析then()回調(diào)函數(shù)中返回的仍是一個(gè)Promise,這個(gè)Promise有可能是我們自己的,有可能是別的庫實(shí)現(xiàn)的,也有可能是一個(gè)具有then()方法的對(duì)象,所以這里靠resolvePromise()來實(shí)現(xiàn)統(tǒng)一處理。

下面是翻譯自PromiseA+規(guī)范關(guān)于resolvePromise()的要求:

Promise 解決過程

Promise 解決過程是一個(gè)抽象的操作,其需輸入一個(gè) promise 和一個(gè)值,我們表示為 [[Resolve]](promise, x),如果 x 有 then 方法且看上去像一個(gè) Promise ,解決程序即嘗試使 promise 接受 x 的狀態(tài);否則其用 x 的值來執(zhí)行 promise 。

這種 thenable 的特性使得 Promise 的實(shí)現(xiàn)更具有通用性:只要其暴露出一個(gè)遵循 Promise/A+ 協(xié)議的 then 方法即可;這同時(shí)也使遵循 Promise/A+ 規(guī)范的實(shí)現(xiàn)可以與那些不太規(guī)范但可用的實(shí)現(xiàn)能良好共存。

運(yùn)行 [[Resolve]](promise, x) 需遵循以下步驟:

x 與 promise 相等

如果 promise 和 x 指向同一對(duì)象,以 TypeError 為據(jù)因拒絕執(zhí)行 promise

x 為 Promise

如果 x 為 Promise ,則使 promise 接受 x 的狀態(tài):

- 如果 x 處于等待態(tài), promise 需保持為等待態(tài)直至 x 被執(zhí)行或拒絕
- 如果 x 處于執(zhí)行態(tài),用相同的值執(zhí)行 promise
- 如果 x 處于拒絕態(tài),用相同的據(jù)因拒絕 promise

x 為對(duì)象或函數(shù)

如果 x 為對(duì)象或者函數(shù):

- 把 x.then 賦值給 then
- 如果取 x.then 的值時(shí)拋出錯(cuò)誤 e ,則以 e 為據(jù)因拒絕 promise
- 如果 then 是函數(shù),將 x 作為函數(shù)的作用域 this 調(diào)用之。傳遞兩個(gè)回調(diào)函數(shù)作為參數(shù),第一個(gè)參數(shù)叫做 resolvePromise ,第二個(gè)參數(shù)叫做 rejectPromise:
    - 如果 resolvePromise 以值 y 為參數(shù)被調(diào)用,則運(yùn)行 [[Resolve]](promise, y)
    - 如果 rejectPromise 以據(jù)因 r 為參數(shù)被調(diào)用,則以據(jù)因 r 拒絕 promise
    - 如果 resolvePromise 和 rejectPromise 均被調(diào)用,或者被同一參數(shù)調(diào)用了多次,則優(yōu)先采用首次調(diào)用并忽略剩下的調(diào)用
    - 如果調(diào)用 then 方法拋出了異常 e:
        - 如果 resolvePromise 或 rejectPromise 已經(jīng)被調(diào)用,則忽略之
        - 否則以 e 為據(jù)因拒絕 promise
    - 如果 then 不是函數(shù),以 x 為參數(shù)執(zhí)行 promise
- 如果 x 不為對(duì)象或者函數(shù),以 x 為參數(shù)執(zhí)行 promise

如果一個(gè) promise 被一個(gè)循環(huán)的 thenable 鏈中的對(duì)象解決,而 [[Resolve]](promise, thenable) 的遞歸性質(zhì)又使得其被再次調(diào)用,根據(jù)上述的算法將會(huì)陷入無限遞歸之中。算法雖不強(qiáng)制要求,但也鼓勵(lì)施者檢測(cè)這樣的遞歸是否存在,若檢測(cè)到存在則以一個(gè)可識(shí)別的 TypeError 為據(jù)因來拒絕 promise。

參考上述規(guī)范,結(jié)合代碼中的注釋,相信大家可以理解resolvePromise()的作用了。

測(cè)試:

test.js

let MyPromise = require("./MyPromise.js");

let promise = new MyPromise(function(resolve, reject) {
  setTimeout(function() {
    resolve(123);
  }, 1000);
});

promise.then((value) => {
  console.log("value1", value);
  return new MyPromise((resolve, reject) => {
    resolve(456);
  }).then((value) => {
    return new MyPromise((resolve, reject) => {
      resolve(789);
    })
  });
}, (reason) => {
  console.log("reason1", reason);
}).then((value) => {
  console.log("value2", value);
}, (reason) => {
  console.log("reason2", reason);
});

打印結(jié)果:

value1 123  
value2 789
6. 讓then()方法的回調(diào)函數(shù)總是異步調(diào)用

官方Promise實(shí)現(xiàn)的回調(diào)函數(shù)總是異步調(diào)用的:

console.log("start");

let promise = new Promise((resolve, reject) => {
  console.log("step-");
  resolve(123);
});

promise.then((value) => {
  console.log("step--");
  console.log("value", value);
});

console.log("end");

打印結(jié)果:

start   
step-
end
step--
value1 123

Promise屬于微任務(wù),這里我們?yōu)榱朔奖阌煤耆蝿?wù)setTiemout來代替實(shí)現(xiàn)異步,具體關(guān)于宏任務(wù)、微任務(wù)以及Event Loop可以參考我的另一篇文章帶你徹底弄懂Event Loop。

MyPromise.js

MyPromise.prototype.then = function(onFuifilled, onRejected) {
  let self = this;
  let promise2 = null;

  promise2 = new MyPromise((resolve, reject) => {
    if (self.state === PENDING) {
      self.onFulfilledCallbacks.push(() => {
        setTimeout(() => {
          try {
            let x = onFuifilled(self.value);
            self.resolvePromise(promise2, x, resolve, reject);
          } catch (reason) {
            reject(reason);
          }
        }, 0);
      });
      self.onRejectedCallbacks.push(() => {
        setTimeout(() => {
          try {
            let x = onRejected(self.reason);
            self.resolvePromise(promise2, x, resolve, reject);
          } catch (reason) {
            reject(reason);
          }
        }, 0);
      });
    }
  
    if (self.state === FULFILLED) {
      setTimeout(() => {
        try {
          let x = onFuifilled(self.value);
          self.resolvePromise(promise2, x, resolve, reject);
        } catch (reason) {
          reject(reason);
        }
      }, 0);
    }
  
    if (self.state === REJECTED) {
      setTimeout(() => {
        try {
          let x = onRejected(self.reason);
          self.resolvePromise(promise2, x, resolve, reject);
        } catch (reason) {
          reject(reason);
        }
      }, 0);
    }
  });

  return promise2;
};

測(cè)試:

test.js

let MyPromise = require("./MyPromise.js");

console.log("start");

let promise = new MyPromise((resolve, reject) => {
  console.log("step-");
  setTimeout(() => {
    resolve(123);
  }, 1000);
});

promise.then((value) => {
  console.log("step--");
  console.log("value", value);
});

console.log("end");

打印結(jié)果:

start   
step-
end
step--
value1 123

經(jīng)過以上步驟,一個(gè)最基本的Promise就已經(jīng)實(shí)現(xiàn)完了,下面我們會(huì)實(shí)現(xiàn)一些不在PromiseA+規(guī)范的擴(kuò)展方法。

7. 實(shí)現(xiàn)catch()方法

then()方法的onFulfilledonRejected回調(diào)函數(shù)都不是必傳項(xiàng),如果不傳,那么我們就無法接收reject(reason)中的錯(cuò)誤,這時(shí)我們可以通過鏈?zhǔn)秸{(diào)用catch()方法用來接收錯(cuò)誤。舉例:

let promise = new Promise((resolve, reject) => {
  reject("has error");
});

promise.then((value) => {
  console.log("value", value);
}).catch((reason) => {
  console.log("reason", reason);
});

打印結(jié)果:

reason has error

不僅如此,catch()可以作為Promise鏈?zhǔn)秸{(diào)用的最后一步,前面Promise發(fā)生的錯(cuò)誤會(huì)冒泡到最后一個(gè)catch()中,從而捕獲異常。舉例:

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

promise.then((value) => {
  console.log("value", value);
  return new Promise((resolve, reject) => {
    reject("has error1");
  });
}).then((value) => {
  console.log("value", value);
  return new Promise((resolve, reject) => {
    reject("has error2");
  });
}).catch((reason) => {
  console.log("reason", reason);
});

打印結(jié)果:

value 123    
reason has error1

那么catch()方法到底是如何實(shí)現(xiàn)的呢?

答案就是在Promise的實(shí)現(xiàn)中,onFulfilledonRejected函數(shù)是有默認(rèn)值的:

MyPromise.js

MyPromise.prototype.then = function(onFuifilled, onRejected) {
  onFuifilled = typeof onFuifilled === "function" ? onFuifilled : value => {return value;};
  onRejected = typeof onRejected === "function" ? onRejected : reason => {throw reason};
};

MyPromise.prototype.catch = function(onRejected) {
  return this.then(null, onRejected);
};

可以看到,onRejected的默認(rèn)值是把錯(cuò)誤reason通過throw拋出去。由于我們對(duì)于同步代碼的執(zhí)行都是在try...catch中的,所以如果Promise發(fā)生了錯(cuò)誤,如果沒傳onRejected,默認(rèn)的函數(shù)會(huì)把錯(cuò)誤reason拋出,然后會(huì)被promise2捕捉到,作為reject(reason)決議。

catch()實(shí)現(xiàn)就是調(diào)用this.then(null, onRejected),由于promise2reject,所以會(huì)執(zhí)行onRejected回調(diào),于是就捕捉到了第一個(gè)promise的錯(cuò)誤。

總結(jié)來說,then()方法中不傳onRejected回調(diào),Promise內(nèi)部會(huì)默認(rèn)幫你寫一個(gè)函數(shù)作為回調(diào),作用就是throw拋出reject或者try...catch到的錯(cuò)誤,然后錯(cuò)誤reason會(huì)被promise2作為reject(reason)進(jìn)行決議,于是會(huì)被下一個(gè)then()方法的onRejected回調(diào)函數(shù)調(diào)用,而catch只是寫了一個(gè)特殊的then(null, onRejected)而已。

所以,我們?cè)趯?b>Promise的鏈?zhǔn)秸{(diào)用的時(shí)候,在then()中可以不傳onRejected回調(diào),只需要在鏈?zhǔn)秸{(diào)用的最末尾加一個(gè)catch()就可以了,這樣在該鏈條中的Promise發(fā)生的錯(cuò)誤都會(huì)被最后的catch捕獲到。

舉例1:

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

promise.then((value) => {
  // 注意,不會(huì)走這里,因?yàn)榈谝粋€(gè)promise是被reject的
  console.log("value1", value);
  return new Promise((resolve, reject) => {
    reject("has error1");
  });
}).then((value) => {
  console.log("value2", value);
  return new Promise((resolve, reject) => {
    reject("has error2");
  });
}, (reason) => {
  // 注意,這個(gè)then有onRejected回調(diào)
  console.log("reason2", reason);
}).catch((reason) => {
  // 錯(cuò)誤在上一個(gè)then就被捕獲了,所以不會(huì)走到這里
  console.log("reason3", reason);
});

打印結(jié)果:

reason2 123

舉例2:

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

promise.then((value) => {
  console.log("value1", value);
  return new Promise((resolve, reject) => {
    reject("has error1");
  });
}).then((value) => {
  console.log("value2", value);
  return new Promise((resolve, reject) => {
    reject("has error2");
  });
}).catch((reason) => {
  // 由于鏈條中的then都沒有onRejected回調(diào),所以會(huì)一直被冒泡到最后的catch這里
  console.log("reason3", reason);
});

catchthen一樣都是返回一個(gè)新的Promise。有的同學(xué)可能會(huì)有疑問,如果catch中的回調(diào)執(zhí)行也發(fā)生錯(cuò)誤該怎么辦呢,這個(gè)我們后續(xù)在Promise異常處理中再做討論。

打印結(jié)果:

reason3 123
8. 實(shí)現(xiàn)finally方法

finally是某些庫對(duì)Promise實(shí)現(xiàn)的一個(gè)擴(kuò)展方法,無論是resolve還是reject,都會(huì)走finally方法。

MyPromise.js

MyPromise.prototype.finally = function(fn) {
    return this.then(value => {
       fn();
       return value;
    }, reason => {
        fn();
        throw reason;
    });
};
9. 實(shí)現(xiàn)done方法

done方法作為Promise鏈?zhǔn)秸{(diào)用的最后一步,用來向全局拋出沒有被Promise內(nèi)部捕獲的錯(cuò)誤,并且不再返回一個(gè)Promise。一般用來結(jié)束一個(gè)Promise鏈。

MyPromise.js

MyPromise.prototype.done = function() {
    this.catch(reason => {
        console.log("done", reason);
        throw reason;
    });
};
10. 實(shí)現(xiàn)Promise.all方法

Promise.all()接收一個(gè)包含多個(gè)Promise的數(shù)組,當(dāng)所有Promise均為fulfilled狀態(tài)時(shí),返回一個(gè)結(jié)果數(shù)組,數(shù)組中結(jié)果的順序和傳入的Promise順序一一對(duì)應(yīng)。如果有一個(gè)Promiserejected狀態(tài),則整個(gè)Promise.allrejected。

MyPromise.js

MyPromise.all = function(promiseArr) {
  return new MyPromise((resolve, reject) => {
    let result = [];

    promiseArr.forEach((promise, index) => {
      promise.then((value) => {
        result[index] = value;

        if (result.length === promiseArr.length) {
          resolve(result);
        }
      }, reject);
    });
  });
};

test.js

let MyPromise = require("./MyPromise.js");

let promise1 = new MyPromise((resolve, reject) => {
  console.log("aaaa");
  setTimeout(() => {
    resolve(1111);
    console.log(1111);
  }, 1000);
});

let promise2 = new MyPromise((resolve, reject) => {
  console.log("bbbb");
  setTimeout(() => {
    reject(2222);
    console.log(2222);
  }, 2000);
});

let promise3 = new MyPromise((resolve, reject) => {
  console.log("cccc");
  setTimeout(() => {
    resolve(3333);
    console.log(3333);
  }, 3000);
});

Promise.all([promise1, promise2, promise3]).then((value) => {
  console.log("all value", value);
}, (reason) => {
  console.log("all reason", reason);
})

打印結(jié)果:

aaaa    
bbbb
cccc
1111
2222
all reason 2222
3333
11. 實(shí)現(xiàn)Promise.race方法

Promise.race()接收一個(gè)包含多個(gè)Promise的數(shù)組,當(dāng)有一個(gè)Promisefulfilled狀態(tài)時(shí),整個(gè)大的Promiseonfulfilled,并執(zhí)行onFulfilled回調(diào)函數(shù)。如果有一個(gè)Promiserejected狀態(tài),則整個(gè)Promise.racerejected。

MyPromise.js

MyPromise.race = function(promiseArr) {
  return new MyPromise((resolve, reject) => {
    promiseArr.forEach(promise => {
      promise.then((value) => {
        resolve(value);   
      }, reject);
    });
  });
};

test.js

let MyPromise = require("./MyPromise.js");

let promise1 = new MyPromise((resolve, reject) => {
  console.log("aaaa");
  setTimeout(() => {
    resolve(1111);
    console.log(1111);
  }, 1000);
});

let promise2 = new MyPromise((resolve, reject) => {
  console.log("bbbb");
  setTimeout(() => {
    reject(2222);
    console.log(2222);
  }, 2000);
});

let promise3 = new MyPromise((resolve, reject) => {
  console.log("cccc");
  setTimeout(() => {
    resolve(3333);
    console.log(3333);
  }, 3000);
});

Promise.race([promise1, promise2, promise3]).then((value) => {
  console.log("all value", value);
}, (reason) => {
  console.log("all reason", reason);
})

打印結(jié)果:

aaaa    
bbbb
cccc
1111
all reason 1111
2222
3333
12. 實(shí)現(xiàn)Promise.resolve方法

Promise.resolve用來生成一個(gè)fulfilled完成態(tài)的Promise,一般放在整個(gè)Promise鏈的開頭,用來開始一個(gè)Promise鏈。

MyPromise.js

MyPromise.resolve = function(value) {
  let promise;

  promise = new MyPromise((resolve, reject) => {
    this.prototype.resolvePromise(promise, value, resolve, reject);
  });

  return promise;
};

test.js

let MyPromise = require("./MyPromise.js");

MyPromise.resolve(1111).then((value) => {
  console.log("value1", value);
  return new MyPromise((resolve, reject) => {
    resolve(2222);
  })
}).then((value) => {
  console.log("value2", value);
})

打印結(jié)果:

value1 1111    
value2 2222

由于傳入的value有可能是普通值,有可能是thenable,也有可能是另一個(gè)Promise,所以調(diào)用resolvePromise進(jìn)行解析。

12. 實(shí)現(xiàn)Promise.reject方法

Promise.reject用來生成一個(gè)rejected失敗態(tài)的Promise。

MyPromise.js

MyPromise.reject = function(reason) {
  return new MyPromise((resolve, reject) => {
    reject(reason);
  });
};

test.js

let MyPromise = require("./MyPromise.js");

MyPromise.reject(1111).then((value) => {
  console.log("value1", value);
  return new MyPromise((resolve, reject) => {
    resolve(2222);
  })
}).then((value) => {
  console.log("value2", value);
}).catch(reason => {
  console.log("reason", reason);
});

打印結(jié)果:

reason 1111
13. 實(shí)現(xiàn)Promise.deferred方法

Promise.deferred可以用來延遲執(zhí)行resolvereject

MyPromise.js

MyPromise.deferred = function() {
    let dfd = {};
    dfd.promies = new MyPromise((resolve, reject) => {
      dfd.resolve = resolve;
      dfd.rfeject = reject;
    });
    return dfd;
};

這樣,你就可以在外部通過調(diào)用dfd.resolve()dfd.reject()來決議該Promise。

13. 如何停止一個(gè)Promise

假設(shè)這樣一個(gè)場(chǎng)景,我們有一個(gè)很長(zhǎng)的Promise鏈?zhǔn)秸{(diào)用,這些Promise是依次依賴的關(guān)系,如果鏈條中的某個(gè)Promise出錯(cuò)了,就不需要再向下執(zhí)行了,默認(rèn)情況下,我們是無法實(shí)現(xiàn)這個(gè)需求的,因?yàn)?b>Promise無論是then還是catch都會(huì)返回一個(gè)Promise,都會(huì)繼續(xù)向下執(zhí)行thencatch。舉例:

new Promise(function(resolve, reject) {
  resolve(1111)
}).then(function(value) {
  // "ERROR!!!"
}).catch()
  .then()
  .then()
  .catch()
  .then()

有沒有辦法讓這個(gè)鏈?zhǔn)秸{(diào)用在ERROR!!!的后面就停掉,完全不去執(zhí)行鏈?zhǔn)秸{(diào)用后面所有回調(diào)函數(shù)呢?

我們自己封裝一個(gè)Promise.stop方法。

MyPromise.js

MyPromise.stop = function() {
  return new Promise(function() {});
};

stop中返回一個(gè)永遠(yuǎn)不執(zhí)行resolve或者rejectPromise,那么這個(gè)Promise永遠(yuǎn)處于pending狀態(tài),所以永遠(yuǎn)也不會(huì)向下執(zhí)行thencatch了。這樣我們就停止了一個(gè)Promise鏈。

new MyPromise(function(resolve, reject) {
  resolve(1111)
}).then(function(value) {
  // "ERROR!!!"
  MyPromise.stop();
}).catch()
  .then()
  .then()
  .catch()
  .then()

但是這樣會(huì)有一個(gè)缺點(diǎn),就是鏈?zhǔn)秸{(diào)用后面的所有回調(diào)函數(shù)都無法被垃圾回收器回收。

14. 如何解決Promise鏈上返回的最后一個(gè)Promise出現(xiàn)錯(cuò)誤

看如下例子:

new Promise(function(resolve) {
  resolve(42)
}).then(function(value) {
  a.b = 2;
});

這里a不存在,所以給a.b賦值是一個(gè)語法錯(cuò)誤,onFulfilled回調(diào)函數(shù)是包在try...catch中執(zhí)行的,錯(cuò)誤會(huì)被catch到,但是由于后面沒有thencatch了,這個(gè)錯(cuò)誤無法被處理,就會(huì)被Promise吃掉,沒有任何異常,這就是常說的Promise有可能會(huì)吃掉錯(cuò)誤。

那么我們?cè)趺刺幚磉@種情況呢?

方法一

就是我們前面已經(jīng)實(shí)現(xiàn)過的done()。

new Promise(function(resolve) {
  resolve(42)
}).then(function(value) {
  a.b = 2;
}).done();

done()方法相當(dāng)于一個(gè)catch,但是卻不再返回Promise了,注意done()方法中不能出現(xiàn)語法錯(cuò)誤,否則又無法捕獲了。

方法二

普通錯(cuò)誤監(jiān)聽windowerror事件可以實(shí)現(xiàn)捕獲

window.addEventListener("error", error => {
  console.log(error); // 不會(huì)觸發(fā)
});

Promise沒有被onRejected()處理的錯(cuò)誤需要監(jiān)聽unhandledrejection事件

window.addEventListener("unhandledrejection", error => {
  console.log("unhandledrejection", error); // 可以觸發(fā),而且還可以直接拿到 promise 對(duì)象
});
14. 單元測(cè)試 結(jié)束

相關(guān)單元測(cè)試以及完整代碼可以到我的github查看,如果對(duì)你有幫助的話,就來個(gè)star吧~

歡迎關(guān)注我的公眾號(hào)

參考文檔

PromiseA+規(guī)范

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://www.ezyhdfw.cn/yun/98050.html

相關(guān)文章

  • Promise進(jìn)階——如何實(shí)現(xiàn)一個(gè)Promise

    摘要:,表示當(dāng)前中的方法的第一個(gè)回調(diào)函數(shù)。在函數(shù)執(zhí)行時(shí),我們會(huì)創(chuàng)建一個(gè)新的,然后將傳入的兩個(gè)回調(diào)函數(shù)用新的的屬性保存下來。首先我們需要先創(chuàng)建一個(gè)新的用于返回,保證后面用戶調(diào)用函數(shù)進(jìn)行后續(xù)邏輯處理時(shí)可以設(shè)置新的和這兩個(gè)回調(diào)函數(shù)。 概述 從上次更新Promise/A+規(guī)范后,已經(jīng)很久沒有更新博客了。之前由于業(yè)務(wù)需要,完成了一個(gè)TypeScript語言的Promise庫。這次我們來和大家一步一步介...

    Clect 評(píng)論0 收藏0
  • 0開始構(gòu)建自己的前端知識(shí)體系-JS-跟著規(guī)范學(xué)Promise

    摘要:本文僅限瀏覽器環(huán)境測(cè)試,環(huán)境可能會(huì)不一致狀態(tài)一個(gè)實(shí)例只能處于三種狀態(tài)中的一種。每次創(chuàng)建的實(shí)例都會(huì)處于狀態(tài),并且只能由變?yōu)榛驙顟B(tài)??梢哉J(rèn)為在實(shí)現(xiàn)里與中的都為解決程序。 前言 Promise作為ES6極為重要的一個(gè)特性,將我們從無限的回調(diào)地獄中解脫出來,變?yōu)殒準(zhǔn)降木帉懟卣{(diào),大大提高的代碼的可讀性。 使用Promise是極為簡(jiǎn)單的,但只停留在會(huì)使用階段還是會(huì)讓我們不知不覺踩到一些坑的。本文會(huì)...

    kelvinlee 評(píng)論0 收藏0
  • 使用角度漸進(jìn)式剖析Promise源碼

    摘要:規(guī)范鏈接規(guī)范中文鏈接本篇文章將從的使用角度來剖析源碼具體實(shí)現(xiàn)。但是對(duì)于則不一樣,回調(diào)函數(shù)通過遞歸調(diào)用自己從而保證其值不為類型才結(jié)束,并將賦值到數(shù)組,最后直到所有的數(shù)組都處理完畢由統(tǒng)一的方法結(jié)束當(dāng)前的操作,進(jìn)入處理流程。 開篇 最近在 github 上看到了一個(gè) extremely lightweight Promise polyfill 實(shí)現(xiàn),打開源碼發(fā)現(xiàn)只有240行,果然極其輕量級(jí),...

    DTeam 評(píng)論0 收藏0
  • 0開始擼一個(gè)Promise(一)

    摘要:說白了,就是給聲明的添加一個(gè)包含空的對(duì)象,再由函數(shù)返回這個(gè)空。如此構(gòu)成一個(gè)層層包裹的鏈。四首先本質(zhì)是一個(gè)遞歸函數(shù),結(jié)束條件是,即終止到未掛載對(duì)象的子為止??梢钥吹竭@個(gè)函數(shù)的作用就是根據(jù)屬性逐個(gè)觸發(fā)鏈中的或函數(shù)。 背景 Promise是一種非常實(shí)用的異步編程方案,本文對(duì)于Promise源碼的分析可以增進(jìn)讀者對(duì)于Promise原理的理解,以后不再害怕異步編程,不用擔(dān)心callback he...

    MSchumi 評(píng)論0 收藏0
  • Promise——閱讀文檔簡(jiǎn)單實(shí)現(xiàn)(二)

    摘要:在和方法執(zhí)行的時(shí)候訂閱事件,將自己的回調(diào)函數(shù)綁定到事件上,屬性是發(fā)布者,一旦它的值發(fā)生改變就發(fā)布事件,執(zhí)行回調(diào)函數(shù)。實(shí)現(xiàn)和方法的回調(diào)函數(shù)都是,當(dāng)滿足條件對(duì)象狀態(tài)改變時(shí),這些回調(diào)會(huì)被放入隊(duì)列。所以我需要在某個(gè)變?yōu)闀r(shí),刪除它們綁定的回調(diào)函數(shù)。 前言 按照文檔說明簡(jiǎn)單地實(shí)現(xiàn) ES6 Promise的各個(gè)方法并不難,但是Promise的一些特殊需求實(shí)現(xiàn)起來并不簡(jiǎn)單,我首先提出一些不好實(shí)現(xiàn)或者容...

    dinfer 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<