異步的發展歷程總結

1. 異步

所謂"異步" 簡單說就是一個任務分紅兩段, 先執行第一段, 而後轉而執行其餘任務, 等作好了準備, 再回過頭來執行第二段.html

高階函數

函數既能夠做爲參數,也能夠做爲返回值jquery

高階函數的英文名叫 Higher-Order Function,熟悉 React 的朋友應該知道高階組件 Higher-Order Component。沒錯,React 的高階組件本質上就是高階函數。編程

那麼,什麼是高階函數呢?promise

高階函數源自於函數式編程,是函數式編程的基本技術。異步

那麼,JS做爲一門「一切皆爲對象」的語言,是如何擁有函數式編程的能力呢?async

是由於在JS中函數是一等公民,即函數能夠被賦值給變量,被變量引用,這便使得函數能夠做爲參數,在其餘函數間相互傳遞:函數式編程

函數當一個參數傳遞

/** * 數值轉換 * @param {Number} val 要被處理的數值 * @param {Function} fn 處理輸入的val * @return {Number || String} */
const toConvert = function(val, fn) {
    return fn(val);
};

const addUnitW = function(val) {
    return val + 'W';
};

toConvert(123.1, Math.ceil); // 124
toConvert(123.1, addUnitW); // "123.1W"
複製代碼

函數當一個返回值

// 判斷一個變量的類型

function isType(type) {
  return function (param) {
    return Object.prototype.toString.call(param) == `[object ${type}]`
  }
}
let isString = isType('String')
let isArray = isType('Array')
console.log(isString('nan')); // true
console.log(isArray({}));  // false
複製代碼

函數即作參數又作返回值

假設有這樣一個需求, 有個函數須要連續調用三次才能夠執行, 你能夠提早思考一下怎麼寫異步編程

function eat() {
  console.log("吃完了");
}
function after(time, fn) {
  let count = 0
  return function () {
    if(count++ === time) {
      fn()
    }
  }
}
let newEat = after(3, eat)
newEat()
newEat()
newEat()
複製代碼

異步編程的語法目標, 就是怎麼樣讓它更像同步編程同樣

  • 回調函數
  • 事件監聽
  • 發佈訂閱
  • Promise/A+ 和 生成器函數
  • async/await

回調

所謂回調函數, 就是把任務的第二段單獨寫在一個函數裏面, 等到從新執行這個任務的時候, 就直接調用這個函數函數

fs.readFile('某個文件', function(err, data) {
  if(err) throw err
  console.log(data)
})
複製代碼

回調函數的問題:佈局

  1. 沒法捕獲錯誤 try catch
  2. 不能return
  3. 回調地獄 (如先讀取A接口的數據,再根據A接口返回的數據去讀取B接口的數據再根據B的返回的內容讀取C..)

事件監聽

發佈訂閱

// 利用發佈訂閱的模式, 讀取html模板和數據, 當二者都存在時輸出
const Event = require('events')
const fs = require('fs')

let eve = new Event()
let html = {}

eve.on('ready', function (key, value) {
  html[key] = value
  // 當兩個結果都有了
  if(Object.keys(html).length == 2) {
    console.log(html);
  }
})  // 註冊

fs.readFile('./template.txt','utf8', function (err, template) {
  eve.emit('ready', 'data', template)
})
fs.readFile('./data.txt', 'utf8', function (err, data) {
  eve.emit('ready', 'data', data)
})


/********************************************/
// 以上這樣方式簡寫的形式能夠用一個"哨兵函數來監聽"

const fs = require('fs')
/* let html = {} // 哨兵函數 function done (key, value) { html[key] = value if(Object.keys(html).length == 2) { console.log(html); } } **/
// 給哨兵函數封裝一下
/**********************************************/
function reader (length, fn) {
  let html = {}
  return function (key, value) {
    html[key] = value
    if(Object.keys(html).length == length) {
      fn(html)
    }
  }
}
let done = reader(2, function (html) {
  console.log(html)
})
/**********************************************/
fs.readFile('./data.txt','utf8', function (err, template) {
  done('template', template)
})
fs.readFile('./demo.txt', 'utf8', function (err, data) {
  done('data', data)
})
複製代碼

生成器

生成器是一個函數, 能夠用來生成迭代器

Generator 函數是 ES6 提供的一種異步編程解決方案,整個 Generator 函數就是一個封裝的異步任務,或者說是異步任務的容器。異步操做須要暫停的地方,都用 yield 語句註明。

function* gen() {
    let a = yield 111;
    console.log(a);
    let b = yield 222;
    console.log(b);
    let c = yield 333;
    console.log(c);
    let d = yield 444;
    console.log(d);
}
let t = gen();
//next方法能夠帶一個參數,該參數就會被看成上一個yield表達式的返回值
t.next(1); //第一次調用next函數時,傳遞的參數無效
t.next(2); //a輸出2;
t.next(3); //b輸出3; 
t.next(4); //c輸出4;
t.next(5); //d輸出5;
複製代碼

爲了讓你們更好的理解上面代碼是如何執行的,我畫了一張圖,分別對應每一次的next方法調用:

Promise

手寫promise

Promise.all的原理

function gen(times, fn) {
  return function (i, data) {
    if(i+1 == times) {
      fn(data)
    }
  }
}

Promise.all = function (promises) {
  return new Promise(function (resolve, reject) {
    let done = gen(promises.length, resolve)
    for (let i = 0; i < promises.length; i++) {
      promises[i].then(function (data) {
        done(i, data)
      }, reject)
    }
  })
}

Promise.race的原理:
Promise.race = function (promises) {
  return new Promise(function (resolve, reject) {
    for (let i = 0; i < promises.length; i++) {
      promises[i].then(resolve, reject)
    }
  })
}
複製代碼

Promise 的業界實現都有哪些?

q(早期angular項目中用過) bluebird 這兩個庫

let fs = require('fs');
let bluebird = require('bluebird'); // bluebird中的一個方法, 可讓異步轉化爲promise
let read = bluebird.promisify(fs.readFile);

read('1.txt','utf-8').then(function(data){
    console.log(data);
})

***********實現原理*************
function promisify(fn) { 
    return function (...args) {
        return new Promise(function (resolve, reject) {
            fn(...args, function (err, data) {
                if (err) reject(err);
                resolve(data);
            })
        })
    }
}
複製代碼
function *read () {
    console.log('開始');
    let a = yield readFile('1.txt');
    console.log(a)
    let b = yield readFile('2.txt');
    console.log(=b)
    let c = yield readFile('3.txt');
    console.log(c)
    return c;
}

function co(it){
    return new Promise((resolve,reject)=>{
        function next(data){
            let { value,done } = it.next(data)
            if(!done){
                value.then((data)=>{
                    next(data)
                },reject)
            }else{
                resolve(value)
            }
        }
        next()
    })
}

複製代碼

手寫promise簡單例子

// 完整代碼 也順便帶你們理順一下
function Promise(executor) {
    let self = this;
    self.value = undefined;  // 成功的值
    self.reason = undefined;  // 失敗的值
    self.status = 'pending'; // 目前promise的狀態pending
    self.onResolvedCallbacks = []; // 可能new Promise的時候會存在異步操做,把成功和失敗的回調保存起來
    self.onRejectedCallbacks = [];
    function resolve(value) { // 把狀態更改成成功
        if (self.status === 'pending') { // 只有在pending的狀態才能轉爲成功態
            self.value = value;
            self.status = 'resolved';
            self.onResolvedCallbacks.forEach(fn => fn()); // 把new Promise時異步操做,存在的成功回調保存起來
        }
    }
    function reject(reason) {  // 把狀態更改成失敗
        if (self.status === 'pending') { // 只有在pending的狀態才能轉爲失敗態
            self.reason = reason;
            self.status = 'rejected';
            self.onRejectedCallbacks.forEach(fn => fn()); // 把new Promise時異步操做,存在的失敗回調保存起來
        }
    }
    try {
        // 在new Promise的時候,當即執行的函數,稱爲執行器
        executor(resolve, reject);
    } catch (e) { // 若是執行executor拋出錯誤,則會走失敗reject
        reject(e);
    }
}

// 這個函數爲核心,全部的promise都遵循這個規範
// 主要是處理then中返回的值x和promise2的關係
function resolvePromise(promise2,x,resolve,reject){
    // 當promise2和then返回的值x爲同一個對象時,變成了本身等本身,會陷入死循環
    if(promise2 === x){
        return reject(new TypeError('Chaining cycle'));
    }
    let called;
    // x多是一個promise也多是一個普通值
    if(x!==null && (typeof x=== 'object' || typeof x === 'function')){
        try{
            let then = x.then; 
            if(typeof then === 'function'){
                then.call(x,y=>{ 
                    if(called) return; 
                    called = true;
                    resolvePromise(promise2,y,resolve,reject);
                },err=>{ 
                    if(called) return;
                    called = true;
                    reject(err);
                });
            }else{
                resolve(x);
            }
        }catch(e){
            if(called) return;
            called = true;
            reject(e);
        }
    }else{ 
        resolve(x);
    }
}

// then調用的時候,都是屬於異步,是一個微任務
// 微任務會比宏任務先執行
// onFulfilled爲成功的回調,onRejected爲失敗的回調
Promise.prototype.then = function (onFulfilled, onRejected) {
    onFulfilled = typeof onFulfilled === 'function'?onFulfilled:val=>val;
    onRejected = typeof onRejected === 'function'?onRejected: err=>{throw err}
    let self = this;
    let promise2;
    // 上面講了,promise和jquery的區別,promise不能單純返回自身,
    // 而是每次都是返回一個新的promise,才能夠實現鏈式調用,
    // 由於同一個promise的pending resolve reject只能更改一次
    promise2 = new Promise((resolve, reject) => {
        if (self.status === 'resolved') {
            // 爲何要加setTimeout?
            // 首先是promiseA+規範要求的
            // 其次是你們寫的代碼,有的是同步,有的是異步
            // 因此爲了更加統一,就使用爲setTimeout變爲異步了,保持一致性
            setTimeout(()=>{
                try { // 上面executor雖然使用try catch捕捉錯誤
                      // 可是在異步中,不必定可以捕捉,因此在這裏
                      // 用try catch捕捉
                    let x = onFulfilled(self.value);
                    // 在then中,返回值多是一個promise,因此
                    // 須要resolvePromise對返回值進行判斷
                    resolvePromise(promise2,x,resolve,reject);
                } catch (e) {
                    reject(e);
                }
            },0)
        }
        if (self.status === 'rejected') {
            setTimeout(()=>{
                try {
                    let x = onRejected(self.reason);
                    resolvePromise(promise2,x,resolve,reject);
                } catch (e) {
                    reject(e);
                }
            },0)
        }
        if (self.status === 'pending') {
            self.onResolvedCallbacks.push(() => {
                setTimeout(()=>{
                    try {
                        let x = onFulfilled(self.value);
                        resolvePromise(promise2,x,resolve,reject);
                    } catch (e) {
                        reject(e);
                    }
                },0)
            });
            self.onRejectedCallbacks.push(() => {
                setTimeout(()=>{
                    try {
                        let x = onRejected(self.reason);
                        resolvePromise(promise2,x,resolve,reject);
                    } catch (e) {
                        reject(e);
                    }
                },0)
            });
        }
    });
    return promise2
}
Promise.defer = Promise.deferred = function(){
    let dfd = {};
    dfd.promise = new Promise((resolve,reject)=>{
        dfd.resolve = resolve;
        dfd.reject = reject;
    })
    return dfd;
}
module.exports = Promise;

複製代碼

異步的終極方案 async await

async await解決了異步問題:

  1. 可讓代碼像同步

  2. 可使用try catch

  3. 可使用promise

  4. 若是let r1 = await 後面等待的是promise,那麼會把promise的結果賦值給前面的r1,若是let r1 = await 後面等待的是普通值,那麼就會把這個普通值賦值給前面的r1

設爲 Flex 佈局之後,子元素的 float 、 clear 和 vertical-align 屬性將失效

async函數的實現, 就是將Generator函數和自動執行器,包裝到一個函數中

async function read() {
    let template = await readFile('./template.txt');
    let data = await readFile('data.txt');
    return template + '+' + data
}

//等同於
function read () {
    return co(function *() {
        let template = yield readFile('template.txt');
        let data = yield readFile('data.txt');
        return template + '+' + data
    })
}
複製代碼
相關文章
相關標籤/搜索