ES6之異步流程的前世此生(上)

ES6之異步流程的前世此生(上)

本文講述了異步流程的演變過程。那麼是什麼是異步編程呢: 簡單來說就是執行一個指令不會立刻返回結果而執行下一個任務,而是等到特定的事件觸發後,才能獲得結果。javascript

基礎知識

咱們知道 javascript 運行在瀏覽器中,以 Google 瀏覽器爲例子, v8 引擎,包含 內存堆: 這是內存分配發生的地方。 調用棧: 這是你代碼執行的地方。html

運行一個函數時,解析器把該函數添加到棧中而且執行這個函數。

Web APIs: DOM、AJAX、Timeout(setTimeout)

js是一門單線程的語言, 這意味這它只有一個調用棧。

當咱們堆棧執行的函數須要大量時間時,瀏覽器會中止響應,幸運的是咱們有異步回調。

javaScript引擎 運行在宿主環境中(瀏覽器或者 node),
CallbackQueue  and Event Loop

事件循環和回調隊列
調用棧和回調隊列,當棧爲空時,它會調取出隊列中的第一個事件,放到調用棧中執行;

常見的 macro-task(這個隊列也被叫作 task queue) 好比: setTimeout、setInterval、 setImmediate、script(總體代碼)、 I/O 操做、UI 渲染等。

常見的 micro-task 好比: process.nextTick、Promise、Object.observe、MutationObserver 等。

promise 永遠會在隊列尾部添加微觀任務

爲何Promise的代碼(microtask)會比setTimeout的代碼(macrotask)更優先執行,由於它太機智了,居然會插隊!
複製代碼

常見的異步編程方案

  1. 回調函數
  2. 事件監聽
  3. 發佈/訂閱
  4. promise對象

環境配置

一雙能敲代碼的手、一臺能執行代碼的電腦。 須要預先引入的庫java

const fs = require('fs')
  
  const co = require('co')
  
  const util = require('util')
複製代碼

callback()

第一階段:回調函數node

function readFile(cb) {
    fs.readFile('./package.json', (err, data) => {
        if (err) return cb(err)
        cb(null, data)
    })
}

readFile((err, data) => {
    if (!err) {
        data = JSON.parse(data)
        console.log(data.name)
    }
})
複製代碼

回調函數的弊端:jquery

  1. 代碼書寫順序與執行順序不一致,不利於維護
  2. 回調函數大可能是匿名函數,bug 追蹤困難
  3. 異步操做的代碼變動,後期維護麻煩。

事件監聽

採用了事件驅動模型,任務的執行不取決與代碼的順序,取決於某個事件是否發生。編程

function f1() {

    setTimeout(function() {

         // f1的任務代碼

          f1.trigger('done');

      }, 1000);

  }
複製代碼

發佈/訂閱

假定咱們存在一個任務中心,當某個事件完成以後,咱們就發射狀態信號,調度中心能夠通知訂閱了該狀態信號的其餘任務。這個也稱爲觀察者模式。json

jQuery.subscribe("done", f2);

 function f1(){

 setTimeout(function () {

 // f1的任務代碼

 jQuery.publish("done");

   }, 1000);

 }

 當f1 執行完成後, 向信號中心"jquery"發佈"done"信號,從而引起f2的執行。


複製代碼

promise

第二階段:Promisepromise

定義階段:promise(resolve, reject)分別成功或者失敗時處理什麼。瀏覽器

調用階段:經過then函數實現,成功就執行resolve,它會將reslove的值傳遞給最近的then函數,做爲then函數的參數。若是出錯reject,那麼交給catch來捕獲異常markdown

promise的要點以下:

  1. 遞歸: 每一個一步操做返回的都是promise對象
  2. 狀態機: 三種狀態peomise對象內部能夠控制,不能在外部改變狀態
  3. 全局異常處理

將回調函數中的結果延後到 then 函數裏處理或交給全局異常處理

咱們約定將每一個函數的返回值都得是 promise 對象。 只要是 promise 對象, 就能夠控制狀態並支持 then 方法,將無限個 promise 對象連接在一塊兒。

hello('xx.html').then(log).then(function() {
    return world('./xxx.js').then(log)
}).catch(err => {
    console.log(err)
})
複製代碼

每一個 promise 對象都有 then 方法, 也就是說then方法是定義在原型對象promise.prototype上的, 它的做用是爲 promise 實例添加狀態改變時的回調函數

Promise.prototype.then() = function (success, fail) {
  this.done(success)
  this.fail(fail)
  return this
}
複製代碼

通常狀況下,只傳 success 回調函數便可,fail函數可選,使用catch來捕獲函數異常比經過fail函數進行處理更加可控。

const requireDirectory = require(require-directory )
module.export = requireDirectory(module)

function readFileAsync(path) {
    return new Promise((resolve, reject) => {
        fs.readFile(path, (err, data) => {
            if (err) reject(err)
            else resolve(data)
        })
    })
}

readFileAsync('./package.json')
    .then(data => {
        data = JSON.parse(data)
        console.log(data.name)
    })
    .catch(err => {
        console.log(err)
    })
複製代碼

咱們來看看下面這個例子:

new Promise(function (resolve) {
    resolve(1)
  }).then(function (value) {
    console.log(value)
  })



  Promise.resolve(1).then(function (value) {
    console.log(' Promise.resovle' + value)
  })

  var error = new Error('this is a error')

複製代碼

new Promise更爲強大,Promise.resolve更爲便捷

如下是更爲便捷的寫法

function hello(i){
  return Promise.resolve(i)
}
hello(1).then(function(){
  console.log('promise.reslove1=' + value)
})

// Promise.resolve返回的是prmise對象,至關於 new Promise(resolve,reject)實例

// Promise.prototype.then()方法的語法以下
p.then(onFulfilled,onRejected);
// p.then(function(value)){}

p.catch(onRejected)
// p.catch(function(reson)) {}


複製代碼
//整個promise 還有這種寫法
hell('./xx.json').then(function (data) {
  return new Promise(function (reslove, reject) {
    console.log('promise ' + data)
    reslove(data)
  })
}).then(function (data) {
  return new Promise(function (reslove, reject) {
    console.log('promise ' + data)
    reslove(data)
  })
}).then(function (data) {
  return new Promise(function (reslove, reject) {
    console.log('promise ' + data)
    reslove(data)
  })
}).catch(function (err) {
  console.log(err)
})

複製代碼

promise原理

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

function MyPromise(executor){
  // executor:這是實例Promise對象時在構造器中傳入的參數,通常是一個function(resolve,reject){}
  let that = this;
  this.status = PENDING;
  this.value = undefined;
  this.reason = undefined;
  this.onFulfilledCallbacks = [];
  this.onRejectedCallbacks = [];


  function resolve(value){
    if(value instanceof Promise) {
        return value.then(resolve, reject);
    }
    // 要確保 onFulfilled 和 onRejected 方法異步執行
    setTimeout(() => {
        // 調用resolve 回調對應onFulfilled函數
        if (that.status === PENDING) {
            // 只能由pedning狀態 => fulfilled狀態 (避免調用屢次resolve reject)
            that.status = FULFILLED;
            that.value = value;
            that.onFulfilledCallbacks.forEach(cb => cb(that.value));
        }
    });

  }
  function reject(reason){
    setTimeout(() => {
        // 調用reject 回調對應onRejected函數
        if (that.status === PENDING) {
            // 只能由pedning狀態 => rejected狀態 (避免調用屢次resolve reject)
            that.status = REJECTED;
            that.reason = reason;
            that.onRejectedCallbacks.forEach(cb => cb(that.reason));
        }
    });
  }
  // executor方法可能會拋出異常,須要捕獲
  try {
    executor(resolve, reject);
  } catch (e) {
      reject(e);
  }
}

MyPromise.prototype.then = function(onFulfilled, onRejected) {
  //獲取下this
  let self = this;

  if(this.status === FULFILLED){
    onFulfilled(self.value)
  }

  if(this.status === REJECTED){
    onRejected(self.value)
  }
  //異步時處理
  if(this.status === PENDING){
    //保存回調函數
    this.onFulfilledCallbacks.push(()=>{
      onFulfilled(self.value)
    });

    this.onRejectedCallbacks.push(()=>{
      onRejected(self.reason)
    })
  }
};


var mp = new MyPromise((resolve, reject) => {
    console.log(11111);
    setTimeout(() => {
        resolve(22222);
        console.log(3333);
    }, 1000);
});
mp.then(x => {
    console.log(x);
    console.log('4444')
}, (err) => {
    console.log('err2', err);
})

複製代碼

上述代碼就是一個簡單的promise了,可是還有兩點問題沒有解決1.鏈式調用 2.不傳值時。 咱們改造下

MyPromise.prototype.then = function(onFulfilled, onRejected) {
  //獲取下this
  let that = this;

  //鏈式調用 在建立一個promise
  let promsie2 = null;

  //解決onFulfilled、onRejected沒有傳值的問題
  onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : y => y
  onRejected = typeof onRejected === 'function' ? onRejected : err => {throw err}


    if (that.status === FULFILLED) { // 成功態
        return newPromise = new MyPromise((resolve, reject) => {
            setTimeout(() => {
                try{
                    let x = onFulfilled(that.value);
                    resolvePromise(newPromise, x, resolve, reject); // 新的promise resolve 上一個onFulfilled的返回值
                } catch(e) {
                    reject(e); // 捕獲前面onFulfilled中拋出的異常 then(onFulfilled, onRejected);
                }
            });
        })
    }

    if (that.status === REJECTED) { // 失敗態
        return newPromise = new MyPromise((resolve, reject) => {
            setTimeout(() => {
                try {
                    let x = onRejected(that.reason);
                    resolvePromise(newPromise, x, resolve, reject);
                } catch(e) {
                    reject(e);
                }
            });
        });
    }

    if (that.status === PENDING) { // 等待態
        // 將onFulfilled/onRejected收集暫存到集合中
        return newPromise = new MyPromise((resolve, reject) => {
            that.onFulfilledCallbacks.push((value) => {
                try {
                    let x = onFulfilled(value);
                    resolvePromise(newPromise, x, resolve, reject);
                } catch(e) {
                    reject(e);
                }
            });
            that.onRejectedCallbacks.push((reason) => {
                try {
                    let x = onRejected(reason);
                    resolvePromise(newPromise, x, resolve, reject);
                } catch(e) {
                    reject(e);
                }
            });
        });
    }

  })

};
複製代碼

resolvePromsie 是什麼呢: Promise A+ 2.27規範

/** * resolve中的值幾種狀況: * 1.普通值 * 2.promise對象 * 3.thenable對象/函數 */

/** * 對resolve 進行改造加強 針對resolve中不一樣值狀況 進行處理 * @param {promise} promise2 promise1.then方法返回的新的promise對象 * @param {[type]} x promise1中onFulfilled的返回值 * @param {[type]} resolve promise2的resolve方法 * @param {[type]} reject promise2的reject方法 */
function resolvePromise(promise2, x, resolve, reject) {
    if (promise2 === x) {  // 若是從onFulfilled中返回的x 就是promise2 就會致使循環引用報錯
        return reject(new TypeError('循環引用'));
    }

    let called = false; // 避免屢次調用
    // 若是x是一個promise對象 (該判斷和下面 判斷是否是thenable對象重複 因此無關緊要)
    if (x instanceof Promise) { // 得到它的終值 繼續resolve
        if (x.status === PENDING) { // 若是爲等待態需等待直至 x 被執行或拒絕 並解析y值
            x.then(y => {
                resolvePromise(promise2, y, resolve, reject);
            }, reason => {
                reject(reason);
            });
        } else { // 若是 x 已經處於執行態/拒絕態(值已經被解析爲普通值),用相同的值執行傳遞下去 promise
            x.then(resolve, reject);
        }
        // 若是 x 爲對象或者函數
    } else if (x != null && ((typeof x === 'object') || (typeof x === 'function'))) {
        try { // 是不是thenable對象(具備then方法的對象/函數)
            let then = x.then;
            if (typeof then === 'function') {
                then.call(x, y => {
                    if(called) return;
                    called = true;
                    resolvePromise(promise2, y, resolve, reject);
                }, reason => {
                    if(called) return;
                    called = true;
                    reject(reason);
                })
            } else { // 說明是一個普通對象/函數
                resolve(x);
            }
        } catch(e) {
            if(called) return;
            called = true;
            reject(e);
        }
    } else {
        resolve(x);
    }
}
複製代碼

測試代碼

var p1 = new MyPromise(function (resolve) {
  setTimeout(function () {
    resolve(1);
  }, 1000);
})



p1.then(function (val) {
  console.log(val)
  var p3 = new MyPromise(function (resolve) {
    setTimeout(function () {
      resolve(val + 1);
    }, 1000);
  });
  return p3;
}).then(function (val) {
  console.log(val);
  var p4 = new MyPromise(function (resolve) {
    setTimeout(function () {
      resolve(val + 1);
    }, 1000);
  });
  return p4
}).then(function (val){
  console.log(val);
  var p4 = new MyPromise(function (resolve) {
    setTimeout(function () {
      resolve(val + 1);
    }, 1000);
  });
});

複製代碼

增長Promise.resolve、catch、race 方法

// 用於promise方法鏈時 捕獲前面onFulfilled/onRejected拋出的異常
MyPromise.catch = function(onRejected) {
  return this.then(null, onRejected);
}

MyPromise.resolve = function (value) {
  return new Promise(resolve => {
      resolve(value);
  });
}

MyPromise.race = function(promises) {
  return new Promise((resolve, reject) => {
      promises.forEach((promise, index) => {
         promise.then(resolve, reject);
      });
  });
}
複製代碼
相關文章
相關標籤/搜索