# 手寫實現Promise
## Promise異步編程
  異步編程簡介:無論在瀏覽器環境還是node環境都需要JavaScript的異步編程,如在瀏覽器環境中的定時器、事件、ajax等或是node環境中的文件讀取、事件等。伴隨著異步編程就有回調機制,異步編程免不了回調。
異步編程問題:產生回調地獄,難于維護和擴展。
       try、catch只能捕捉同步代碼中出現的異常。
       同步并發的異步操作存在一定的問題。
解決方案: ES6 Promise可以解決回調地獄,以及同步并發的異步問題。
  jQuery的Callbacks和Lodash的after都是解決回調問題的其他方法
### Promise使用
```js
//excutor function 同步執行
let promise = new Promise((resolve,reject)=>{
? ? //異步操作
? ? setTimeout(()=>{
? ? ? ? Math.random()*100 > 60 ? resolve("ok"): reject("fail");
? ? },1000);
});
//Promise內部狀態,pending(等待),onFulFilled(成功),onReject(失敗)
//注冊回調,異步執行
//宏任務(setTimeout)? ? 微任務
//微任務有優先執行權
//then 可以鏈式操作
//上一個then不拋出錯誤的話,下一個then會執行成功函數
//返回值作為下一個then注冊函數的執行參數
//如果返回值為Promise對象,則下一個then的執行取決于該對象的執行函數
promise.then((val)=>{//微任務
? ? console.log(val);
? ? return new Promise((resolve,reject)=>{
? ? ? ? reject("newPromise:fail");
? ? });
},(reason)=>{
? ? console.log(reason);
? ? return "fail then1:param";
}).then((val)=>{
? ? console.log("ok then2:",val);
},(reason)=>{
? ? console.log("fail then2:",reason);
});
```
#### then 注冊回調返回值
#### catch 異常捕獲
```js
let promise = new Promise((resolve,reject)=>{
? ? throw new Error("test error");
});
//失敗函數捕獲
promise.then(null,(reason)=>{
? ? console.log(reason);
});
//鏈式調用時如果有空then,則相當于不存在可忽視
//catch捕獲
//catch后面可以繼續鏈式調用
promise.then().catch((error)=>{
? ? console.log(error);
? ? }).then((val)=>{
? ? ? ? console.log(val,"after catch: ok");
? ? },(reason)=>{
? ? ? ? console.log(reason,"after ctach: fail");
? ? })
```
#### finally 最后處理函數
#### Promise.all 同步并發異步的結果
```js
let oPro = new Promise(()=>{});
//Promise.all參數為數組,數組元素必須為Promise對象,其會將
//多個Promise實例包裝成一個新的Promise實例。
//全部成功時數組內元素的返回值組成數組,只要有失敗時返回最先被reject失敗
//狀態的值
Promise.all([oPro,oPro,oPro]).then((val)=>{
? ? console.log(val);//val為數組
});
```
#### Promise.race 誰先成功處理誰
```js
let oPro = new Promise(()=>{});
//Promise.race([p1,p2,p3]);里面的哪個結果獲得的快,就返回那個結果,
//不管結果本身成功或失敗。誰的狀態先發生改變就返回誰的狀態
Promise.race([oPro,oPro,oPro]).then((val)=>{
? ? console.log(val);
},(reason)=>{
? ? console.log(reason);
});
```
### Promise模擬實現
點擊查看 [Promise規范][promise-standard]
```js
//考慮兼容性,用ES5實現
function MyPromise(excutor) {
? ? this.status = "pending";
? ? this.resolveValue = null;
? ? this.rejectReason = null;
? ? this.resolveCallbackList = [];
? ? this.rejectCallbackList = [];
? ? try {
? ? ? ? excutor(this.resolve.bind(this), this.reject.bind(this));
? ? } catch (e) {
? ? ? ? this.reject(e);
? ? }
}
MyPromise.prototype = {
? ? resolve(val) {
? ? ? ? if (this.status === "pending") {
? ? ? ? ? ? this.status = "FulFilled";
? ? ? ? ? ? this.resolveValue = val;
? ? ? ? ? ? this.resolveCallbackList.forEach(function (cbFn) {
? ? ? ? ? ? ? ? cbFn();
? ? ? ? ? ? });
? ? ? ? }
? ? },
? ? reject(reason) {
? ? ? ? if (this.status === "pending") {
? ? ? ? ? ? this.status = "Rejected";
? ? ? ? ? ? this.rejectReason = reason;
? ? ? ? ? ? this.rejectCallbackList.forEach(function (cbFn) {
? ? ? ? ? ? ? ? cbFn();
? ? ? ? ? ? });
? ? ? ? }
? ? },
? ? then(onFulFilled, onRejected) {
? ? ? ? var self = this;
? ? ? ? self._dealNullThen(onFulFilled)._dealNullThen(onRejected);
? ? ? ? return new MyPromise(function (resolve, reject) {
? ? ? ? ? ? if (self.status === "FulFilled") {
? ? ? ? ? ? ? ? //模擬異步執行,此為宏任務,底層代碼為微任務
? ? ? ? ? ? ? ? setTimeout(function () {
? ? ? ? ? ? ? ? ? ? try {
? ? ? ? ? ? ? ? ? ? ? ? var nextResolveValue = onFulFilled(self.resolveValue);
? ? ? ? ? ? ? ? ? ? ? ? self._dealReturnValPromse(nextResolveValue, resolve, reject);
? ? ? ? ? ? ? ? ? ? } catch (e) {
? ? ? ? ? ? ? ? ? ? ? ? reject(e);
? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? }, 0);
? ? ? ? ? ? } else if (self.status === "Rejected") {
? ? ? ? ? ? ? ? setTimeout(function () {
? ? ? ? ? ? ? ? ? ? try {
? ? ? ? ? ? ? ? ? ? ? ? var nextRejectValue = onRejected(self.rejectReason);
? ? ? ? ? ? ? ? ? ? ? ? self._dealReturnValPromse(nextRejectValue, resolve, reject, true);
? ? ? ? ? ? ? ? ? ? } catch (e) {
? ? ? ? ? ? ? ? ? ? ? ? reject(e);
? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? }, 0);
? ? ? ? ? ? } else if (self.status === "pending") {
? ? ? ? ? ? ? ? self.resolveCallbackList.push(function () {
? ? ? ? ? ? ? ? ? ? setTimeout(function () {
? ? ? ? ? ? ? ? ? ? ? ? try {
? ? ? ? ? ? ? ? ? ? ? ? ? ? var nextResolveValue = onFulFilled(self.resolveValue);
? ? ? ? ? ? ? ? ? ? ? ? ? ? self._dealReturnValPromse(nextResolveValue, resolve, reject);
? ? ? ? ? ? ? ? ? ? ? ? } catch (e) {
? ? ? ? ? ? ? ? ? ? ? ? ? ? reject(e);
? ? ? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? ? ? }, 0);
? ? ? ? ? ? ? ? });
? ? ? ? ? ? ? ? self.rejectCallbackList.push(function () {
? ? ? ? ? ? ? ? ? ? setTimeout(function () {
? ? ? ? ? ? ? ? ? ? ? ? try {
? ? ? ? ? ? ? ? ? ? ? ? ? ? var nextRejectValue = onRejected(self.rejectReason);
? ? ? ? ? ? ? ? ? ? ? ? ? ? self._dealReturnValPromse(nextRejectValue, resolve, reject, true);
? ? ? ? ? ? ? ? ? ? ? ? } catch (e) {
? ? ? ? ? ? ? ? ? ? ? ? ? ? reject(e);
? ? ? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? ? ? }, 0);
? ? ? ? ? ? ? ? });
? ? ? ? ? ? }
? ? ? ? });
? ? },
? ? _dealNullThen(fn) { //處理空then情況
? ? ? ? if (!fn) {
? ? ? ? ? ? fn = function (val) {
? ? ? ? ? ? ? ? return val;
? ? ? ? ? ? }
? ? ? ? }
? ? ? ? return this;
? ? },
? ? _dealReturnValPromse(returnVal, resolve, reject, isRejected) {
? ? ? ? if (returnVal instanceof MyPromise) {
? ? ? ? ? ? //若返回值為MyPromise對象,則后面的執行狀態由該對象來決定
? ? ? ? ? ? returnVal.then(function (val) {
? ? ? ? ? ? ? ? resolve(val);
? ? ? ? ? ? }, function (reason) {
? ? ? ? ? ? ? ? reject(reason);
? ? ? ? ? ? });
? ? ? ? } else {
? ? ? ? ? ? //如果返回值不為MyPromise對象,則執行回調函數
? ? ? ? ? ? if (!isRejected) {
? ? ? ? ? ? ? ? resolve(returnVal);
? ? ? ? ? ? } else {
? ? ? ? ? ? ? ? reject(returnVal);
? ? ? ? ? ? }
? ? ? ? }
? ? },
};
MyPromise.race = function (promiseArr) {
? ? return new MyPromise(function (resolve, reject) {
? ? ? ? promiseArr.forEach(function (ele) {
? ? ? ? ? ? ele.then(resolve, reject);
? ? ? ? });
? ? });
};
//全部成功才執行成功回調,只要有一個失敗就執行失敗回調
MyPromise.all = function (promiseArr) {
? ? return new MyPromise(function (resolve, reject) {
? ? ? ? var returnValueArr = [],
? ? ? ? ? ? count = 0;
? ? ? ? for (var i = 0, len = promiseArr.length; i < len; i++) {
? ? ? ? ? ? (function (i) {
? ? ? ? ? ? ? ? promiseArr[i].then(function (val) {
? ? ? ? ? ? ? ? ? ? returnValueArr[i] = val;
? ? ? ? ? ? ? ? ? ? if (++count == len) {
? ? ? ? ? ? ? ? ? ? ? ? resolve(returnValueArr);
? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? }, function (reason) {
? ? ? ? ? ? ? ? ? ? reject(reason);
? ? ? ? ? ? ? ? });
? ? ? ? ? ? }(i));
? ? ? ? }
? ? });
};
```
## ES6 Symbol
數據結構:第七種數據結構Symbol
特點:唯一,可作為對象的屬性,有靜態屬性Symbol.iterator
## ES6 Iterator
  **迭代器目的**:標準化迭代操作。
  **迭代模式**:提供一種方法可以順序獲得聚合對象中的各個元素,是一種最簡單也是最常見的設計模式。它可以讓用戶透過特定的接口巡訪集合中的每一個元素而不用了解底層的實現。
  **迭代器簡介**:依照與迭代模式的思想而實現,分內部迭代器和外部迭代器。
    **內部迭代器**:本身是函數,該函數內部定義好迭代規則,完全接受整個迭代過程,外部只需要一次初始調用。
    Array.prototype.forEach、jQuery.each內部迭代器
    **外部迭代器**:本身是函數,執行返回迭代對象,迭代下一個元素必須顯式調用,調用復雜度增加,但靈活性增強。
    function outerIterator(){}外部迭代器
```js
//模擬寫自己外部迭代器
function OuterIterator(o){
? ? let curIndex=0;
? ? let next=()=>{
? ? ? ? return {
? ? ? ? ? ? value:o[curIndex],
? ? ? ? ? ? done:o.length == ++curIndex,
? ? ? ? }
? ? }
? ? return {
? ? ? ? next,
? ? }
}
let arr=[1,2,3];
let oIt=outerIterator(arr);
oIt.next();
oIt.next();
oIt.next();
```
### 部署Iterator
```js
let obj={
? ? 0:"a",
? ? 1:"b",
? ? 2:"c",
? ? length:3,
? ? //要能迭代,必須部署Iterator,符合ES6
? ? [Symbol.iterator]:function (){
? ? ? ? let curIndex=0;
? ? ? ? let next = () => {
? ? ? ? ? ? return {
? ? ? ? ? ? ? ? value: this[curIndex],
? ? ? ? ? ? ? ? done: this.length == curIndex++,
? ? ? ? ? ? }
? ? ? ? };
? ? ? ? return{
? ? ? ? ? ? next,
? ? ? ? }
? ? },
}
console.log([...obj]);
```
### Generator
Generator簡介:生成器,本身為函數,執行后返回迭代對象,函數內部要配合yield使用,Generator函數分段執行,遇到yield即暫停。
特點:
  function和函數名之間需要帶*
  函數體內yield表達式,產出不同的內部狀態(值)
```js
//示例 Generator產生迭代對象
function *test(){
? ? let val1= yield "a";
? ? console.log(val1);//val1的值為第二次next中傳入的值
? ? yield "b";
? ? yield "c";
? ? return "d";
}
let oG=test();
oG.next();//{value:"a",done:false}
oG.next();//{value:"b",done:false}
oG.next();//{value:"c",done:false}
oG.next();//{value:"d",done:true}
```
改造前面的代碼
```js
let obj={
? ? 0:"a",
? ? 1:"b",
? ? 2:"c",
? ? length:3,
? ? //要能迭代,必須部署Iterator,符合ES6
? ? [Symbol.iterator]:function (){
? ? ? ? let curIndex=0;
? ? ? while(curIndex != this.length){
? ? ? ? ? yield this[curIndex++];
? ? ? };
? ? },
}
console.log([...obj]);
```
Generator函數使用
```js
function *read(path){
? ? let val1 = yield readFile(path);
? ? let val2 = yield readFile(val1);
? ? let val3 = yield readFile(val2);
? ? return val3;
}
let oG = read();
let {value, done} = oG.next();
value.then((val)=>{
? ? let {value, done} = oG.next();
? ? value.then((val)=>{
? ? ? ? let {value, done} = oG.next();
? ? ? ? value.then((val)=>{
? ? ? ? ? ? console.log(val);
? ? ? ? });
? ? });
});
//遞歸優化
function Co(oIterator){
? ? return new Promise((res,rej)=>{
? ? ? ? let next = (data)=>{
? ? ? ? ? ? let {value, done} = oIterator.next(data);
? ? ? ? ? ? if(done){
? ? ? ? ? ? ? ? res(value);
? ? ? ? ? ? }else{
? ? ? ? ? ? ? ? value.then((val)=>{
? ? ? ? ? ? ? ? ? ? next(val);
? ? ? ? ? ? ? ? },rej);
? ? ? ? ? ? }
? ? ? ? };
? ? ? ? next();
? ? });
}
//使用
Co(read()).then((val)=>{
? ? console.log(val);
});
```
#### Promise化
```js
let fs = require("fs");
let path="./data.txt";
let format="utf-8";
//原始函數
function readFile(){
? ? return new Promise((res,rej)=>{
? ? ? ? fs.readFile(path,format,(err,data)=>{
? ? ? ? ? ? ? ? if(err){
? ? ? ? ? ? ? ? ? ? rej(err);
? ? ? ? ? ? ? ? }else{
? ? ? ? ? ? ? ? ? ? res(data);
? ? ? ? ? ? ? ? }
? ? ? ? ? ? });
? ? });
}
//對函數進行promise化? (npm i bluebird)
function promisify(fn){
? ? return (...arg)=>{
? ? ? ? return new Promise((res,rej)=>{
? ? ? ? ? ? fn(...arg,(err,data)=>{
? ? ? ? ? ? ? ? if(err){
? ? ? ? ? ? ? ? ? ? rej(err);
? ? ? ? ? ? ? ? }else{
? ? ? ? ? ? ? ? ? ? res(data);
? ? ? ? ? ? ? ? }
? ? ? ? ? ? });
? ? ? ? });
? ? };
}
let readFilePromisify=promisify(fs.readFile);
readFilePromisify(path,format).then((val)=>{
? ? console.log(val);
});
//進一步對對象內異步方法進行promise化
function promisifyAll(){
? ? for(let key in obj){
? ? ? ? let fn=obj[key];
? ? ? ? if(typeof fn === "function"){
? ? ? ? ? ? obj[key + "Async"] = promisify(fn);
? ? ? ? }
? ? }
}
promisifyAll(fs);
fs.readFileAsync(path,format).then((val)=>{
? ? console.log(val);
});
```
### async & await
async簡介:async函數,是Generator語法糖,通過babel編譯后可以看出它就是Generator+Promise+Co(遞歸)思想實現的,配合await使用。
目的:優雅的解決異步操作問題。
```js
//解決回調地獄
//try catch
//同步并發的異步結果
async function read(path){
? ? try{
? ? ? ? let val1 = await readFile(path);
? ? ? ? let val2 = await readFile(val1);
? ? ? ? let val3 = await readFile(val2);
? ? }catch(e){
? ? ? ? console.log(e);//能夠捕獲異常
? ? }
? ? return val3;
}
read(path).then((val)=>{
? ? console.log(val);
});
//解決同步并發的異步問題
//Promise.all有局限性,一個異常其他也不能出結果
Promise.all([readFile(path1),readFile(path2),readFile(path3)])
.then((val)=>{
? ? console.log(val);
},(reason)=>{
? ? console.log(reason);
});
//使用async和await可以解決
```