以前的博客在簡書和
github.io
:https://chijitui.github.io/,但感受簡書好像涼涼了就搬家來掘金了,以後會不按期更關於 Node.js 設計模式和微服務的東西,歡迎投喂點關注,愛您!javascript
在 Javascript 中, 並不是全部的異步控制函數和庫都支持開箱即用的promise
,因此在大多數狀況下都須要吧一個典型的基於回調的函數轉換成一個返回promise
的函數,好比:java
function promisify(callbackBaseApi) {
return function promisified() {
const args = [].slice.call(arguments);
return new Promise((resolve, reject) => {
args.push((err, result) => {
if(err) {
return reject(err);
}
if(arguments.length <= 2) {
resolve(result);
} else {
resolve([].slice.call(arguments, 1));
}
});
callbackBaseApi.apply(null, args);
});
}
}
複製代碼
如今的 Node.js 核心實用工具庫util
裏面已經支持(err, value) => ...
回調函數是最後一個參數的函數, 返回一個返回值是一個promise
版本的函數。git
在看異步控制流模式以前,先開始分析順序執行流。按順序執行一組任務意味着一次運行一個任務,一個接一個地運行。執行順序很重要,必須保留,由於列表中任務運行的結果可能影響下一個任務的執行,好比:github
start -> task1 -> task2 -> task3 -> end
複製代碼
這種流程通常都有着幾個特色:算法
這種執行流直接用在阻塞的 API 中並無太多問題,可是,在咱們使用非阻塞 API 編程的時候就很容易引發回調地獄。好比:編程
task1(err, (callback) => {
task2(err, (callbakck) => {
task3(err, (callback) => {
callback();
});
});
});
複製代碼
傳統的解決方案是進行任務的拆解方法就是把每一個任務拆開,經過抽象出一個迭代器,在任務隊列中去順序執行任務:設計模式
class TaskIterator {
constructor(tasks, callback) {
this.tasks = tasks;
this.index = 0;
this.callback = callback;
}
do() {
if(this.index === this.tasks.length) {
return this.finish();
}
const task = tasks[index];
task(() => {
this.index++;
this.do();
})
}
finish() {
this.callback();
}
}
const tasks = [task1, task2, task3];
const taskIterator = new TaskIterator(tasks, callback);
taskIterator.do();
複製代碼
須要注意的是, 若是task()
是一個同步操做的話,這樣執行任務就可能變成一個遞歸算法,可能會有因爲再也不每個循環中釋放堆棧而達到調用棧最大限制的風險。數組
順序迭代 模式是很是強大的,由於它能夠適應好幾種狀況。例如,能夠映射數組的值,能夠將操做的結果傳遞給迭代中的下一個,以實現 reduce 算法,若是知足特定條件,能夠提早退出循環,甚至能夠迭代無線數量的元素。 ------ Node.js 設計模式(第二版)promise
值得注意的是,在 ES6 裏,引入了promise
以後也能夠更簡便地抽象出順序迭代的模式:bash
const tasks = [task1, task2, task3];
const didTask = tasks.reduce((prev, task) =>{
return prev.then(() => {
return task();
})
}, Promise.resolve());
didTask.then(() => {
// do callback
});
複製代碼
在一些狀況下,一組異步任務的執行順序並不重要,咱們須要的僅僅是任務完成的時候獲得通知,就能夠用並行執行流程來處理,例如:
-> task1
/
start -> task2 (allEnd callback)
\
-> task3
複製代碼
不做要求的時候,在 Node.js 環境下編程就可開始放飛自我:
class AsyncTaskIterator {
constructor(tasks, callback) {
this.tasks = tasks;
this.callback = callback;
this.done = 0;
}
do() {
this.tasks.forEach(task => {
task(() => {
if(++this.done === this.tasks.length) {
this.finish();
}
});
});
}
finish() {
this.callback();
}
}
const tasks = [task1, task2, task3];
const asyncTaskIterator = new AsyncTaskIterator(tasks, callback);
asyncTaskIterator.do();
複製代碼
使用promise
也就能夠經過Promise.all()
來接受全部的任務而且執行:
const tasks = [task1, task2, task3];
const didTask = tasks.map(task => task());
Promise.all(didTask).then(() => {
callback();
})
複製代碼
並行編程放飛自我天然是很爽,可是在不少狀況下咱們須要對並行隊列的數量作限制,從而減小資源消耗,好比咱們限制並行隊列最大數爲2
:
-> task1 -> task2
/
start (allEnd callback)
\
-> task3
複製代碼
這時候,就須要抽象出一個並行隊列,在使用的時候對其實例化:
class TaskQueque {
constructor(max) {
this.max = max;
this.running = 0;
this.queue = [];
}
push(task) {
this.queue.push(task);
this.next();
}
next() {
while(this.running < this.max && this.queue.length) {
const task = this.queue.shift();
task(() => {
this.running--;
this.next();
});
this.running++;
}
}
}
const tasks = [task1, task2, task3];
const taskQueue = new TaskQueue(2);
let done = 0, hasErrors = false;
tasks.forEach(task => {
taskQueue.push(() => {
task((err) => {
if(err) {
hasErrors = true;
return callback(err);
}
if(++done === tasks.length && !hasError) {
callback();
}
});
});
});
複製代碼
而用promise
的處理方式也與之類似:
class TaskQueque {
constructor(max) {
this.max = max;
this.running = 0;
this.queue = [];
}
push(task) {
this.queue.push(task);
this.next();
}
next() {
while(this.running < this.max && this.queue.length) {
const task = this.queue.shift();
task.then(() => {
this.running--;
this.next();
});
this.running++;
}
}
}
const tasks = [task1, task2, task3];
const taskQueue = new TaskQueue(2);
const didTask = new Promise((resolve, reject) => {
let done = 0, hasErrors = true;
tasks.forEach(task => {
taskQueue.push(() => {
return task().then(() => {
if(++done === task.length) {
resolve();
}
}).catch(err => {
if(!hasErrors) {
hasErrors = true;
reject();
}
});
});
});
});
didTask.then(() => {
callback();
}).then(err => {
callback(err);
});
複製代碼
那麼問題來了,若是我須要封裝一個庫,使用者須要在callback
和promise
中靈活切換怎麼辦呢?讓別人一直本身切換就會顯得很難用,因此就須要同時暴露callback
和promise
的 API ,讓使用者傳入callback
的時候使用callback
,沒有傳入的時候返回一個promise
:
function asyncDemo(args, callback) {
return new Promise((resolve, reject) => {
precess.nextTick(() => {
// do something
// 報錯產出 err, 沒有則產出 result
if(err) {
if(callback) {
callback(err);
}
return resolve(err);
}
if(callback){
callback(null, result);
}
resolve(result);
});
});
}
複製代碼