異步方法的發展流程

@(同步與異步)[callback|Promise|Generator + Co|Async + Await]javascript

  • 回調函數
  • Promise
  • Generator + Co
  • Async/Await

同步方法

同步順序且連續執行,必須執行完畢或返回後纔會繼續執行後續代碼。html

函數(封裝,私有化)

  • 是由事件驅動的或者當它被調用時執行的可重複使用的代碼塊

高階函數

  • 接受函數做爲輸入:函數能夠當作參數傳遞給另外一個函數
  • 輸出一個函數:一個函數執行後返回一個函數

例:
判斷內容數據類型是否爲指望類型值java

/**
 * 判斷內容值是否爲指望的類型
 * @param {*} content 內容值
 * @param {String} expect 指望值的類型
 * @returns {Boolean} 返回內容值是否爲指望的類型
 */
function isType(content, expect) {
    // 四種判斷類型方法: constructor、typeof、instanceof、Object.prototype.toString
    let type = Object.prototype.toString.call(content).replace(/\[object\s|\]/g, '');
    return type === expect;
}

console.log(isType('hello', 'String'));    // true

擴展:利用高階函數來定義判斷各數據類型的方法ios

/**
 * 返回用於判斷內容值是否爲指望的類型的函數
 * @param {String} expect 指望類型
 * @returns {function(*)} 返回判斷函數
 */
function isType(expect) {
    return function (content) {
        let type = Object.prototype.toString.call(content).replace(/\[object\s|\]/g, '');
        return type === expect;
    }
}

let util = {};
let types = ['Object', 'Array', 'Function', 'String', 'Number', 'Boolean', 'Null', 'Undefined'];

types.forEach((item) => {
    util[`is${item}`] = isType(item);
});

console.log(util.isNumber(86));  // true
console.log(util.isBoolean('true'));  // false

異步方法

異步表示非連續執行,如:setTimeoutAjax、事件等方法,一般會在另外一個線程中執行,不會阻塞主線程。 es6

異步編程的方法,大概有下面四種。編程

  • 回調函數
  • 事件監聽
  • 發佈/訂閱
  • Promise 對象
異步方法若是出錯了不能捕獲 try/catch 錯誤
獲取的結果不能經過 return 返回

回調函數

阮一峯:所謂回調函數,就是把任務的第二段單獨寫在一個函數裏面,等到從新執行這個任務的時候,就直接調用這個函數。它的英語名字 callback,直譯過來就是"從新調用"。(原文)數組

雖然回調函數多用於異步編程,但帶有回調函數的方法不必定是異步的。promise

問題1:
第二個請求是依賴於第一個請求
例:瀏覽器

// error-first
// 回調方法執行以前拋出的錯誤,程序沒法捕捉,只能看成參數傳入回調方法
fs.readFile('./1.txt', 'utf8', function (err, a) {
    fs.readFile('./2.txt', 'utf8', function (err, b) {
        console.log(a, b);
    });
});

問題2:
兩個異步請求,同時拿到兩個異步請求的結果
例:異步

function after(times, callback) {
    let arr = [];

    return function (data) {
        arr.push(data);
        if(--times === 0){
            callback(arr);
        }
    };
}

let out = after(2, function (data) { 
    console.log(data);
});

out('once');
out('twice');

第二次執行時會一塊兒獲得結果 ['once', 'twice']
能夠理解爲回調方法至關於兩個異步請求有關係。

回調函數可能會出現會出現多重嵌套,從而形成代碼難以管理,提升了額外的維護成本,這種狀況被稱爲 產生回調地獄 (Callback Hell)

發佈/訂閱模式

  • 發佈(這件事發生時 我要依次執行)
  • 訂閱(我預先想到的事)
let fs = require('fs');
let events = {
    cbs: [],
    res: [],
    on(callback) {
        this.cbs.push(callback);
    },
    emit(data) {
        this.res.push(data);
        this.cbs.forEach(item => item(this.res));
    }
};

// 訂閱過程  
events.on((data) => {
    if (data.length === 2) {
        console.log(data);
    }
});

// 訂閱過程  
events.on(() => {
    console.log('good');
});

fs.readFile('file-01.txt', 'utf8', (err, data) => {
    events.emit(data);
});

fs.readFile('file-02.txt', 'utf8', (err, data) => {
    events.emit(data);
});

發佈訂閱同時拿到兩個異步結果,須要回調函數,Promise不須要回調函數

Promise

promise 對象用於表示一個異步操做的最終狀態(完成或失敗),以及其返回的值。
三個狀態 成功態 失敗態 等待態
一個 Promise有如下幾種狀態:

  • pending: 初始等待狀態,既不是成功,也不是失敗狀態。
  • fulfilled: 成功態,意味着操做成功完成。
  • rejected: 失敗態,意味着操做失敗。

默認狀態是等待態
等待態能夠變成 成功態或失敗態
成功就不能失敗
也不能從失敗變成成功
不支持的低版本瀏覽器 須要 es6-promise 模塊

Promise

new Promise 時會傳遞一個執行器 executor
executor 執行器是當即執行的,而且接受兩個函數resolvereject 做爲其參數
每一個 promise 實例都有一個 then 方法,參數是成功和失敗,成功會有成功的值 失敗
同一個 promise 能夠屢次 then

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

promise
    .then((data) => {
        console.log('data', data);
    }, (err) => {
        console.log('err', err);
    });

promise
    .then((data) => {
        console.log('data', data);
    }, (err) => {
        console.log('err', err);
    });

回調地獄

Promise 解決回調地獄
例:

let fs = require('fs');
function read(path, encoding) {
    return new Promise((resolve, reject) => {
        fs.readFile(path, encoding, function (err, data) {
            if (err) return reject(err);
            resolve(data);
        });
    });
}

屢次 then

同一個 promise 能夠屢次 then
成功的回調或者失敗的回調執行後能夠返回一個 promise
會將這個 promise 的執行結果傳遞給下一次 then
若是返回一個普通的值 ,會將這個普通值傳遞倒下一次 then 的成功的參數

read('./file-01.txt', 'utf8')
    .then(data => {
    
        // 由於 read 方法會返回一個 promise ,返回read執行至關於返回一個 promise
        // 會將這個 promise 的執行成功的結果傳遞給下一次 then 的 resolve 
        return read(data, 'utf8');
    }, err => {
        console.log(err);
    })
    .then(data => {
    
        // 若是返回一個普通的值 ,會將這個普通值傳遞倒下一次 then 的成功的參數
        return [data];
    }, err => {
        console.log(err);
    })
    .then(data => {
    
        // 返回的是一個普通值
        console.log(data);
        
        // 沒有寫 return ,至關於返回一個 undefined ,下一個then的成功值則爲 undefined
    }, err => {
        console.log(err);
    })
    .then(data => {
    
        // 上一個 then 沒有返回值,默認值爲 undefined
        // undefined 也算成功
        console.log(data);
        
        // 拋出錯誤,將傳給下一個 then 的 reject 
        throw new Error('xxx');
    }, err => {
        console.log(err);
    })
    .then(null, err => {
    
        // 若是上一個 then 拋出錯誤,最近的 reject 會執行
        // reject 執行後默認下一個 then 會接收 undefined 
        console.log(err);
    })
    .then(data => {
    
        // 上一個 then 中失敗沒有返回值,默認爲 undefined 
        console.log('resolve');        
    }, err => {
        console.log('reject');
    });

代替 reject

能夠將全部 reject 方法都用 catch 代替

read('./file-01.txt', 'utf8')
    .then(data => {
        return read(data, 'utf8');
    })
    .then(data => {
        return [data];
    })
    .then(data => {
        console.log(data);
    })
    .then(data => {
        console.log(data);
        
        // 拋出錯誤後,找到最近的接收錯誤方法
        // 若是全部的 then 都沒有 reject 方法,則找最後一個 catch
        throw new Error('xxx');
    })
    .then(null)
    .then(data => {
        console.log('resolve');        
    })
    .catch(err => {
        console.log(err);
    });

then穿透

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

// 成功不寫的時候,默認: value => value
// 失敗不寫的時候,默認: err => {throw err}
promise
    .then()
    .then(data => {
        
    });

all

解決多個異步請求問題
例:
法等待兩個 promise 都執行完成後,會返回一個新的 promise
若是有一個失敗就失敗了

Promise
    .all([read('file-01.txt', 'utf8'), read('file-02.txt', 'utf8')])
    .then(data => {
        console.log(data);
    }, err => {
        console.log(err);
    });

race

race 一樣返回一個 Promise 其成功與失敗的狀態取決於最早返回結果的狀態
誰跑的快就用誰的結果

Promise
    .race([read('file-01.txt', 'utf8'), read('file-02.txt', 'utf8')])
    .then(data => {
        console.log(data);
    }, err => {
        console.log(err);
    });

有狀態的 promise

建立一個一出生就成功或者失敗的 promise

// 成功態
Promise
    .resolve('123')
    .then(data => {
        console.log(data);
    });
    
Promise
    .reject('123')
    .then(null, err => {
        console.log(err)
    });

鏈式調用返回 this
promise 不能返回 this
promise 實現鏈式調用是靠返回一個新的 promise

Callback 到了 Promise 時代,不須要再去調用回調函數。
Promise 的典型應用 Axios Fetch

但實際上 Promise 只是對回調函數的改進的寫法而已,並非新的語法功能,原來的語義也被一堆 then 破壞掉了。

Generator迭代器(迭代模式)

es6 實現的 generator (生成器)會返回一個內部指針(迭代器)。

例:

for (let i of [1, 2, 3]) {
    console.log(i);
}

例:

function arg() {
    // arguments是一個類數組
    // 有索引,有長度,但不是數組
    // 沒有數組的方法
    // 但能夠被迭代
    // 內置iterator
    for (let i of arguments) {
        console.log(i);
    }
}

arg(1, 2, 3, 4);

例:

let arr = {0: 1, 1: 2, 2: 3, length: 3};

for (let i of arr) {
    console.log(i);
}

// TypeError: arr is not iterable
// arr是不可被迭代的,由於arr沒有迭代器

爲自定義類數組增長迭代方法,迭代方法是幫咱們迭代

* 表明一個生成器,function 關鍵字和方法名之間有個 * ,這就表明一個 generator

生成器生成一個迭代器,配合 yield,遇到 yield 就暫停,

迭代器必須返回一個對象,對象裏有一個 next 方法,調用迭代器的 next 方法移動內部指針,每調用一次 next 方法就能夠返回一個對象 {value,done},對象裏有倆屬性:表示當前階段的信息 ,valueyield 表達式的值,done 是一個表示是否執行完畢的布爾值。

再次調用 next 繼續執行。
遇到 return 時就迭代完成了,沒寫 return 則爲 return undefined,也算完成了

function * thing() {
    // 生成器返回一個迭代器
    // yield產出,而且爲未完成狀態
    yield 1;

    // return表示完成
    return 2;
}

// 返回一個迭代器
let it = thing();

迭代器裏有 next 方法,返回一個對象,包含 donevalue

// { value: 1, done: false }
console.log(it.next());

// { value: 2, done: true }
console.log(it.next());

傳值

function * some() {

    let a = yield 1;
    console.log('a', a);
    let b = yield 2;
    console.log('b', b);

    return b;
}

let so = some();

// 第一個next傳遞參數無效,沒有意義
// 傳了100也沒用
console.log(so.next('100'));     // { value: 1, done: false }

// 第二個next傳參是第一次yield的返回值
console.log(so.next('200'));    // a 200
                                // { value: 2, done: false }

console.log(so.next('300'));    // b 300
                                // { value: '300', done: true }

例:
獲取 1.txt 內容:2.txt 2.txt內容是最終結果

let blueBird = require('bluebird');
let fs = require('fs');

let read = blueBird.promisify(fs.readFile);

function* readFile() {
    let data1 = yield read('1.txt', 'utf8');
    let data2 = yield read(data1, 'utf8');
    return data2;
}

配合 promise 使用:

let it = readFile();

it.next().value
    .then(data => {
        return it.next(data).value;
    })
    .then(data => {
        console.log(data);
    });

配合 co 使用:

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

co(readFile())
    .then(data => {
        console.log(data);
    });

使用 co

let co = require('co');

co(readFile())
    .then(data => {
        console.log(data);
    });

async / await

async await 實際上是一個語法糖
async + await = generator + co

let blueBird = require('bluebird');
let fs = require('fs');

let read = blueBird.promisify(fs.readFile);

async function readFile() {
    let data1 = await read('1.txt', 'utf8');
    let data2 = await read(data1, 'utf8');
    return data2;
}

readFile()
    .then(data => {
        console.log(data);
    });

相關文章
相關標籤/搜索