異步簡單來講就是作一件事時,作到一半可能須要等待系統或服務處理以後纔會獲得響應和結果,此時能夠轉去作另外一件事,等到得到響應以後在去執行剩下一半的事情。反之同步就是一直等到響應而後接着作事,中間不會跳去作別的事。es6
異步發展史能夠簡單概括爲: callback -> promise -> generator + co -> async+await(語法糖)npm
callback也就是咱們常常聽到的回調函數,它會在咱們異步任務執行並收到結果響應後出發,舉個簡單的例子:數組
let fs = require('fs');
fs.readFile('./2.promise/1.txt', 'utf8', function(err, data) {
fs.readFile(data, 'utf8', function(err,data) {
console.log(data);
});
});
複製代碼
(這裏有個點就是異步並不支持try/catch,只有同步方可)promise
callback雖然幫咱們解決了異步問題,可是它仍有一些不足,首先,試想若是上面的嵌套一多,在代碼上看起來就會很亂,以後回來修改邏輯時就會很難入手,一旦修改了一個,它嵌套的回調函數也要跟着改,第二個問題就是沒法合併兩個或多個異步的結果,譬如如下例子:bash
fs.readFile('./2.promise/1.txt', 'utf8', function(err,data) {
});
fs.readFile('./2.promise/2.txt', 'utf8', function(err, data) {
console.log(data);
});
複製代碼
Promise的引入就解決了以上這些問題,首先來看下Promise的簡單用法:併發
let p = new Promise(function(resolve, reject) {
resolve(100);
});
p.then(function(data) {
console.log('data', data);
}, function(err) {
console.log('err', err);
});
複製代碼
能夠看到Promis經過then的鏈式調用解決了嵌套回調的問題,在用法上Promise的構造函數會接受一個executor函數,這個函數帶有兩個參數resolve和reject,兩個參數背後其實就是兩個函數,而經過Promise構造函數建立出來的對象會保存一個status屬性,resolve會作的事就是將這個屬性從初始化的pending轉爲resolved,而reject則是轉爲rejected,同時兩個函數均可以接受一個參數,做爲以後then中回調函數的參數傳入,那麼在then方法中咱們能夠看到它接收兩個參數,第一個就是成功resolved以後會調用的回調函數,第二個就是rejected的回調函數。異步
這裏注意的是,只要狀態轉爲resolved或rejected之中的其中一個,那麼當前promise對象就不能再轉變狀態了。以後無論調resolve仍是reject都會被忽略。async
另外,上面所說Promise是能夠支持鏈式調用的,因此then是能夠屢次調用的,可是由於剛剛所說狀態不可轉變的問題,因此鏈式調用每次then返回的不是當前的Promise對象而是一個新的Promise對象,那麼第2次then的狀態又是怎麼決定的呢,第一次then中不管是成功的回調仍是失敗的回調只要返回告終果就會走下一個then中的成功,若是有錯誤走下一個then的失敗。函數
接下來介紹一些其餘Promise的使用方法:ui
- Promise.all
const p = Promise.all([p1, p2, p3]);
複製代碼
上面代碼中,Promise.all方法接受一個數組做爲參數,p一、p二、p3都是Promise實例,若是不是,就會先調用下面講到的Promise.resolve方法,將參數轉爲 Promise 實例,再進一步處理(Promise.all方法的參數能夠不是數組,但必須具備Iterator接口,且返回的每一個成員都是 Promise實例)
p的狀態由p一、p二、p3決定,分紅兩種狀況.
(1)只有p一、p二、p3的狀態都變成fullfilled,p的狀態纔會變成fullfilled,此時p一、p二、p3的返回值組成一個數組,傳遞給p的回調函數.
(2)只要p一、p二、p3之中有一個被reject,p的狀態就變成rejected,此時第一個被reject的實例的返回值,會傳遞給p的回調函數。
- Promise.race
const p = Promise.race([p1, p2, p3]);
複製代碼
上面代碼中,只要p一、p二、p3之中有一個實例率先改變狀態,p的狀態就跟着改變。那個率先改變的 Promise 實例的返回值,就傳遞給p的回調函數。
Promise.race方法的參數與Promise.all方法同樣,若是不是Promise實例,就會先調用下面講到的Promise.resolve方法,將參數轉爲 Promise實例,再進一步處理。
- Promise.resolve
將現有對象轉爲Promise對象,該實例的狀態爲resolved.
- Promise.reject
將現有對象轉爲Promise對象,該實例的狀態爲rejected.
- Promise.prototype.catch
catch方法其實就是.then(null, rejection)的別名
關於Promise的實現規範有興趣的能夠去看一下,接下來介紹一些現有的封裝了Promise的庫,在本文最後咱們也會一塊兒來完成一個Promise庫的實現:
1. Q庫
能夠經過npm install q安裝使用
Q.fcall(function() {
return 100;
}).then(function(data) {
console.log(data);
})
複製代碼
這裏的fcall其實就相似於Promis.resolve方法
function read(url) {
return new Promise(function(resolve, reject) {
require('fs').readFile(url, 'utf8', function(err, data) {
if (err) reject(err);
resolve(data);
});
});
}
let Q = require('q');
Q.all([read('./2.promise/1.txt'), read('./2.promise/2.txt')]).then(function ([a1, a2]) {
console.log(a1, a2);
});
複製代碼
一樣它也有相似Promise.all這樣的方法
let Q = require('q');
function read(url) {
let defer = Q.defer();
require('fs').readFile(url, 'utf8', function(err, data) {
if (err) defer.reject(err);
defer.resolve(data);
})
return defer.promise;
}
read('./2.promise/1.txt').then(function(data) {
console.log(data);
});
複製代碼
一樣也可使用defer這樣一個語法糖(關於defer會在以後的Promise實現中解釋)
2. Bluebird庫
能夠經過npm install bluebird安裝
它有兩個比較亮點的方法,promisify和promisifyAll
let fs = require('fs');
let bluebird = require('bluebird');
let read = bluebird.promisify(fs.readFile);
bluebird.promisifyAll(fs);
fs.readFileAsync('./2.promise/1.txt', 'utf8').then(function(data) {
console.log(data);
});
複製代碼
所作的就是將傳入的方法所有轉成返回是Promise對象的新函數
背後實現原理其實也很簡單
function promisify(fn) { // promise化 將回調函數在內部進行處理
return function (...args) {
return new Promise(function (resolve, reject) {
fn(...args, function (err, data) {
if (err) reject(err);
resolve(data);
})
})
}
}
function promisifyAll(obj) {
Object.keys(obj).forEach(key => { // es5將對象轉化成數組的方法
if (typeof obj[key] === 'function') {
obj[key + 'Async'] = promisify(obj[key])
}
})
}
複製代碼
關於generato的使用和原理這裏就再也不贅述,你們能夠去參考這裏,這裏就給個簡單的例子:
// genrator函數要用* 來比標識,yield(暫停 產出)
// 它會將函數分割出好多個部分,調用一次next就會繼續向下執行
// 返回結果是一個迭代器 迭代器有一個next方法
// yield後面跟着的是value的值
// yield等號前面的是咱們當前調用next傳進來的值
// 第一次next傳值是無效的
function* read() {
console.log(1);
let a = yield 'zf';
console.log(a);
let b = yield 9;
console.log(b);
return b;
}
let it = read();
console.log(it.next('213')); // {value:'zf',done:false}
console.log(it.next('100')); // {value:9,done:false}
console.log(it.next('200')); // {value:200,done:true}
console.log(it.next('200')); // {value:200,done:true}
複製代碼
generator與Promise的搭配使用例子以下
let bluebird = require('bluebird');
let fs = require('fs');
let read = bluebird.promisify(fs.readFile);
function* r() {
let content1 = yield read('./2.promise/1.txt', 'utf8');
let content2 = yield read(content1, 'utf8');
return content2;
}
let it = r();
it.next().value.then(function(data) { // 2.txt
it.next(data).value.then(function(data) {
console.log(it.next(data).value);
});
})
複製代碼
固然咱們能夠看到調用時仍是得這樣不停的嵌套去獲取值,因此這裏須要引入另外一個叫co的庫,那麼使用方法就能夠變成以下:
let co = require('co');
let bluebird = require('bluebird');
let fs = require('fs');
let read = bluebird.promisify(fs.readFile);
function* r() {
let content1 = yield read('./2.promise/1.txt', 'utf8');
let content2 = yield read(content1, 'utf8');
return content2;
}
co(r()).then(function(data) {
console.log(data)
})
複製代碼
co背後實現的原理其實也不復雜:
function co(it) {
return new Promise(function(resolve, reject) {
function next(d) {
let { value, done } = it.next(d);
if (!done) {
value.then(function (data) { // 2,txt
next(data)
}, reject)
} else {
resolve(value);
}
}
next();
});
}
複製代碼
async+await就是目前爲至,異步的最佳解決方案,它同時解決了
示例代碼:
let bluebird = require('bluebird');
let fs = require('fs');
let read = bluebird.promisify(fs.readFile);
// 用async來修飾函數,aysnc須要配await, await只能接promise
// async和await(語法糖) === co + generator
async function r() {
try{
let content1 = await read('./2.promise/100.txt', 'utf8');
let content2 = await read(content1, 'utf8');
return content2;
} catch(e) { // 若是出錯會catch
console.log('err', e)
}
}
// async函數返回的是promise
r().then(function(data) {
console.log('flag', data);
}, function(err) {
console.log(err);
})
複製代碼