學習Promise && 簡易實現Promise

ntscshen發表於2018-05-30

Promise和非同步

  • Promise非同步解決方案,是其他非同步方案的基石
  • 非同步:現在和將來之間的時間間隙
  • Promises/A+規範

Promise核心方法和輔助方法

  • Promise.prototype.then ( 核心 )
  • Promise.prototype.catch ( 輔助 )
  • Promise.resolve ( 輔助 )
  • Promise.reject ( 輔助 )
  • Promise.all ( 輔助 )
  • Promise.race ( 輔助 )
  • 阮一峰(promise)

Promise

  1. promise 狀態變更後不可逆
  2. 同一個例項可以被多次 then
  3. .then方法是非同步且可以被鏈式呼叫( 返回新的promise )
  4. .then方法中使用者可以顯示的返回 promise ,且可以多層巢狀。例項如下圖

http://occeqxmsk.bkt.clouddn.com/status_-all.png

Promise中也許是最麻煩的部分1 ( 訂閱釋出 )

  1. new Promise 中使用了非同步方法,那麼當前狀態就會一直處於 pending 狀態,直到在非同步的回撥中觸發 resolvereject
  2. 那麼在建構函式 Promise 中,就需要把then裡面的成功和失敗函式,儲存在私有陣列中 this.onResolvedArythis.onRejectedAry
  3. then 方法為 pending 狀態時,把第一個成功引數 onFulFilled 和第二個失敗引數 onRejected,分別儲存到私有變數 this.onResolvedArythis.onRejectedAry
  4. 當非同步回撥中的 resolverejcet 被觸發時,分別遍歷對應的陣列 (onResolvdeAry, onRejectedAry) 並執行即可

Promise中也許是最麻煩的部分2 ( 遞迴 )

預設返回Promise和使用者顯示的返回Promise

  1. defaultReturnPromise.then 中預設返回新的 promise (鏈式呼叫)
  2. userReturnValue:使用者可以顯示的返回 promise 或其他引數
  3. 如果使用者返回的是普通字元,則直接呼叫 defaultReturnPromiseresolve
  4. 如果使用者返回的是一個 promise,則看使用者返回的 promise 內部呼叫的 resolve 還是 reject
  5. 如果呼叫的是 resolve ,則拿著 resolve 的引數直接執行 defaultReturnPromiseresolve
  6. 如果呼叫的是 reject,則拿著這個錯誤引數直接執行 defaultReturnPromisereject
  7. 如果 resolve 中執行的又是一個新的 promise ,則重複執行上述規則(2-6)

new Promise輪廓

class Promise {
  constructor(executor) {
    let resolve = success_value => {};
    let reject = fail_reason => {};
    executor(resolve, reject);
  }
  then(onFulFilled, onRejected) {}
}
複製程式碼

狀態不可逆

Promise/A+ (2.1) promise狀態: 必須處於以下三種狀態中的一種 pending( 等待 )、fulfilled( 完成 )、rejected( 失敗 )

pending 可以轉換成 fulfilledrejected,只要轉換了完成或失敗,則不可逆

class Promise {
  constructor(executor) {
    this.status = 'pending';// 預設狀態
    this.success_data;// 成功傳遞的資料
    this.fail_reason;// 失敗傳遞的資料
    // 執行成功
    let resolve = success_data => {
      if (this.status === 'pending') {
        this.status = 'fulfilled';// 變更為成功狀態
        this.success_data = success_data;// 把內容傳遞進success_data中
      }
    };
    // 執行失敗
    let reject = fail_reason => {
      if (this.status === 'pending') {
        this.status = 'rejected';
        this.fail_reason = fail_reason;
      }
    };
    // new Promise的第一個引數會立即執行
    executor(resolve, reject);
  }
  then(onFulFilled, onRejected) {
    if (this.status === 'fulfilled') {
      onFulFilled(this.success_data);
    }
    if (this.status === 'rejected') {
      onRejected(this.fail_reason);
    }
  }
}
複製程式碼

同一個例項可以被多次then

class Promise {
  constructor(executor) {
    this.status = 'pending';// 預設狀態
    this.success_data;// 成功傳遞的資料
    this.fail_reason;// 失敗傳遞的資料
    this.onResolvedAry = [];// 存放成功的回撥
    this.onRejectedAry = [];// 存放失敗的回撥
    // 執行成功
    let resolve = success_data => {
      if (this.status === 'pending') {
        this.status = 'fulfilled';
        this.success_data = success_data;// 把內容傳遞進來
        this.onResolvedAry.forEach(item => item());// 遍歷快取列表,依次觸發裡面的回撥函式
      }
    };
    // 執行失敗
    let reject = fail_reason => {
      if (this.status === 'pending') {
        this.status = 'rejected';
        this.fail_reason = fail_reason;
        this.onRejectedAry.forEach(item => item());
      }
    };
    // new Promise的第一個引數會立即執行
    executor(resolve, reject);
  }
  then(onFulFilled, onRejected) {
    if (this.status === 'fulfilled') {
      onFulFilled(this.success_data);
    }
    if (this.status === 'rejected') {
      onRejected(this.fail_reason);
    }
    // 非同步執行時,status狀態處於pending狀態
    if (this.status === 'pending') {
      // 同一個例項可以被多次then,在非同步成功後呼叫resolve或者reject方法,以分別執行onResolvedAry,onRejectedAry兩個陣列
      // 新增到快取列表、以供非同步成功後執行的resolve或reject所迴圈呼叫
      this.onResolvedAry.push(onFulFilled(this.success_data));
      this.onRejectedAry.push(onRejected(this.fail_reason));
    }
  }
}
複製程式碼

.then方法是非同步的

// 需要在then的狀態判斷下新增setTimeout(() => {}, 0)來模擬非同步效果
複製程式碼

.then方法預設返回新的Promise(鏈式呼叫)

.then 方法中,狀態判斷下新增 - 返回新的 new Promise( 鏈式呼叫 ),在 promise 新增 setTimeout 以模擬非同步執行

// 需要在then的狀態判斷下新增並返回一個新的Promise
class Promise {
  constructor(executor) {
    this.status = 'pending';// 預設狀態
    this.success_data;// 成功傳遞的資料
    this.fail_reason;// 失敗傳遞的資料
    this.onResolvedAry = [];// 存放成功的回撥
    this.onRejectedAry = [];// 存放失敗的回撥
    // 執行成功
    let resolve = success_data => {
      if (this.status === 'pending') {
        this.status = 'fulfilled';
        this.success_data = success_data;// 把內容傳遞進來
        this.onResolvedAry.forEach(item => item());
      }
    };
    // 執行失敗
    let reject = fail_reason => {
      if (this.status === 'pending') {
        this.status = 'rejected';
        this.fail_reason = fail_reason;
        this.onRejectedAry.forEach(item => item());
      }
    };
    executor(resolve, reject);
  }
  then(onFulFilled, onRejected) {
    let promise2;
    if (this.status === 'fulfilled') {
      promise2 = new Promise((resolve, reject) => {
        setTimeout(() => {
          onFulFilled(this.success_data);
        }, 0);
      });
    }
    if (this.status === 'rejected') {
      promise2 = new Promise((resolve, reject) => {
        setTimeout(() => {
          onRejected(this.fail_reason);
        }, 0);
      });
    }
    // 非同步執行時,status處於pending狀態
    // new Promise的第一個引數會立即執行,
    if (this.status === 'pending') {
      // 同一個例項可以被多次then,在非同步成功後呼叫resolve或者reject方法,以分別執行onResolvedAry,onRejectedAry兩個陣列
      promise2 = new Promise((resolve, reject) => {
        setTimeou(() => {
          this.onResolvedAry.push(onFulFilled(this.success_data));
          this.onRejectedAry.push(onRejected(this.fail_reason));
        }, 0);
      });
    }
  }
}
複製程式碼

.then方法中使用者可以顯示的返回Promise,並可以多層巢狀

擷取其中的一段程式碼

then(onFulFilled, onRejected) {
  let promise2;
  if (this.status === 'fulfilled') {
    promise2 = new Promise((resolve, reject) => {
      setTimeout(() => {
        let x = onFulFilled(this.success_data);
        resolvePromise(promise2, x, resovle, reject);
        // x為使用者輸入的返回值,p.then(data => { return "123" })
        // 如果是非promise的話,此時可以直接呼叫新的promise2的resolve方法
        // 如果是promise,那麼就需要在then中拿到非同步回來的引數,以呼叫promise2的resolve方法
        // 因為resolve或者reject中可以巢狀promise所以,所以我們需要把這個判斷抽離出一個函式,以方便遞迴呼叫自己
      }, 0);
    });
  }
}

resolvePromise(promise2, x, resolve, reject) {
  // typeof null === object
  if(x !== null && (typeof x === 'object' || typeof x === 'function')) {
    let then = x.then;
    // 判斷then是否是函式,也許是一個物件
    if (typeof then === 'function') {
      then.call(x, (data) => {
        // 如果resolve中還是一個promise,需要遞迴處理,直到出現不是promise值為止,然後呼叫promise2的resolve或reject。以返回到主流程的下一個then中
        resolvePromise(promise2, data, resolve, reject);
      }, (error) => {
        reject(error);
      });
    } else {
      // 如果不是一個函式,可能是一個JSON
      resolve(x);
    }
  } else {
    // 如果不是promise就直接呼叫promise2的resolve
    resolve(x)
  }
}
複製程式碼

.then的程式碼

// 解析預設返回的promise和使用者輸入的promise之間的關係
function resolvePromise(promise2, x, resolve, reject) {
  // 這裡面的resolve和reject都是promise2的
  // 判斷x是否是Promise(核心)
  if (promise2 === x) {
    // 2.3.1 如果promise和x指向同一個物件,爆出TypeError型別錯誤,表示拒絕
    return reject(new TypeError('引用錯誤'));
  }
  // 2.3.2 如果x是一個物件或者函式,再去取then方法
  // typeof null === object
  if (x !== null && (typeof x === 'object' || typeof x === 'function')) {
    // 如果去檢索x.then方法,丟擲了異常,那麼久應該reject異常丟擲
    let onOff;// 防止成功之後再調取失敗。。。為了相容別人的Proimse
    try {// 防止取then時,報錯
      let then = x.then;// 2.3.3.1 取x的then方法
      // 2.3.3.3 如果then是一個函式,就認為其實Promise
      if (typeof then === 'function') {
        // 讓x.then執行。使用者輸入的promise執行then方法
        then.call(x, (data) => {
          if (onOff) return;
          onOff = true;
          // data有可能還是一個Promise
          // 遞迴解析Promise
          resolvePromise(promise2, data, resolve, reject);
        }, (error) => {
          if (onOff) return;
          onOff = true;
          reject(error);
        });
      } else {
        resolve(x);
      }
    } catch (error) {
      if (onOff) return;
      onOff = true;
      reject(error);
    }
  } else {// 2.3.4 如果不是object或function 則直接fulfill 成功
    // 如果是一個普通值,則直接呼叫resolve把普通值傳遞進去
    resolve(x);
  }
}

class Promise {
  constructor(executor) {
    this.status = 'pending';
    this.success_value;
    this.fail_reason;
    this.onResolvedCallbacks = [];
    this.onRejectedCallbacks = [];
    let resolve = data => {
      if (this.status === 'pending') {
        this.status = 'fulfilled';// 變更狀態
        this.success_value = data;// 傳遞值
        this.onResolvedCallbacks.forEach(item => item());
      }
    }
    let reject = reason => {
      if (this.status === 'pending') {
        this.status = 'rejected';
        this.fail_reason = reason;
        this.onRejectedCallbacks.forEach(item => item());
      }
    }
    try {
      executor(resolve, reject);
    } catch (error) {
      reject(error);
    }
  }
  then(onFulFilled, onRejected) {
    let promise2;
    // 新增鏈式操作
    if (this.status === 'fulfilled') {
      // 執行完成之後,返回一個新的Promise,以方便下一次鏈式呼叫
      promise2 = new Promise((resolve, reject) => {
        // 檢視onFulFilled執行完的結果是什麼?
        let x = onFulFilled(this.success_value);
        // 如果x返回的是一個普通值,則把這個普通只作為promise2成功的結果
        // 如果x是Promise,則取其結果,作為promise2成功的結果
        // 現在需要判斷x和promise2的關係
        // promise2是then中執行完畢後的回撥Proimse
        // x是 then中使用者輸入寫的return值
        // 如果使用者return的是一個普通值:那麼需要傳遞給Promise2()的成功結果
        // 如果使用者return的是一個Promise:那麼需要判定是成功還是失敗

        // 解析promise,去x的結果,然後讓promise2成功或者失敗
        resolvePromise(promise2, x, resolve, reject);
      });
    }
    if (this.status === 'rejected') {
      promise2 = new Promise((resolve, reject) => {
        let x = onRejected(this.fail_reason);
        resolvePromise(promise2, x, resolve, reject);
      });
    }
    if (this.status === 'pending') {
      promise2 = new Promise((resolve, reject) => {
        // 儲存值,在非同步成功之後迴圈當前陣列執行
        this.onResolvedCallbacks.push(() => {
          let x = onFulFilled(this.success_value);
          resolvePromise(promise2, x, resolve, reject);
        });
        this.onRejectedCallbacks.push(() => {
          let x = onRejected(this.fail_reason);
          resolvePromise(promise2, x, resolve, reject);
        });
      });
    }
    return promise2;
  }
}
複製程式碼

.catch方法

// catch就是then的第二個錯誤的簡寫
catch(onRejected) {
  return this.then(null, onRejected);
}
複製程式碼

Promise.resolve

Promise 上的靜態方法 resolvereject ,直接呼叫靜態方法中返回的新的 Promise 中的resolvereject即可

Promise.resolve = function (value) {
  return new Promise((resolve, reject) => resolve(value));
}
Promise.reject = function (value) {
  return new Promise((resolve, reject) => reject(value));
}
複製程式碼

Promise.all([p1, p2, p3])

  1. 陣列中所有的非同步資料回來後,才執行 then 中成功回撥
  2. 迴圈陣列的 then 方法,如果 then 的數值 all 回來了,則呼叫新的 Promiseresolve,如果沒有全部回來,則調 reject
  3. 如果陣列中的非同步直接調取了 reject 的,則直接呼叫新的 Promisereject
Promise.all = function (promises) {
  return new Promise((resolve, reject) => {
    let arr = [];
    let idx = 0;// 為了保證獲取全部成功,來設定的索引.不能使用arr.length來判斷,如果陣列最後的非同步先回來,那麼此時arr和promises的長度就相等了 - 亂套了
    function processData(index, data) {
      // 和all的陣列一一對應,arr陣列和promises長度相等時成功
      arr[index] = data;
      // 給第二個複製,第一個的數值就是空的undefined,萬一陣列中的最後一個回來他們就都成功了 arr.length === promises.lenght
      idx++;
      if (idx === promises.length) {
        resolve(arr);
      }
    }
    for (let i = 0; i < promises.length; i++) {
      promises[i].then((data) => {
        // all 要根據陣列的順序來存放
        processData(i, data);
      }, reject);
    }
  });
}
複製程式碼

Promise.race

第一個引數中只要有一個非同步資料回來,就執行

Promise.race = function (promises) {
  return new Promise((resolve, reject) => {
    for (let i = 0; i < promises.length; i++) {
      // 只要有一個非同步資料回來,誰回來的早就搞誰
      promises[i].then(resolve, reject);
    })
  });
}
複製程式碼

附:這篇部落格 也許 想表達 promise 其實相對而言比較簡單 (⊙﹏⊙)b

相關文章