async 的本質是一個流程控制。其實在異步編程中,還有一個更爲經典的模型,叫作 Promise/Deferred 模型(固然還有更多相關解決方法,好比 eventproxy,co 等,到時候遇到在挖坑) javascript
首先,咱們思考一個典型的異步編程模型,考慮這樣一個題目:讀取一個文件,在控制檯輸出這個文件內容css
var fs = require('fs');
fs.readFile('1.txt', 'utf8', function (err, data) {
console.log(data);
});
複製代碼
看起來很簡單,再進一步: 讀取兩個文件,在控制檯輸出這兩個文件內容html
var fs = require('fs');
fs.readFile('1.txt', 'utf8', function (err, data) {
console.log(data);
fs.readFile('2.txt', 'utf8', function (err, data) {
console.log(data);
});
});//歡迎加入全棧開發交流圈一塊兒學習交流:864305860
複製代碼
要是讀取更多的文件呢vue
var fs = require('fs');
fs.readFile('1.txt', 'utf8', function (err, data) {
fs.readFile('2.txt', 'utf8', function (err, data) {
fs.readFile('3.txt', 'utf8', function (err, data) {
fs.readFile('4.txt', 'utf8', function (err, data) {
// ...
});
});//歡迎加入全棧開發交流圈一塊兒學習交流:864305860
});
});
複製代碼
這就是傳說中的 callback hell,可使用 async 來改善這段代碼,可是在本例中咱們要用 promise/defer 來改善它 promise 基本概念 首先它是一個對象,它和 javascript 普通的對象沒什麼區別,同時,它也是一種規範,跟異步操做約定了統一的接口,表示一個異步操做的最終結果,以同步的方式來寫代碼,執行的操做是異步的,但又保證程序執行的順序是同步的java
promise 有一個 then 方法,then 方法能夠接受 3 個函數做爲參數。前兩個函數對應 promise 的兩種狀態 fulfilled, rejected 的回調函數。第三個函數用於處理進度信息 爲了理解它,一些重要原理必須記牢:.then() 老是返回一個新的 promise,以下面代碼:node
var promise = readFile()
var promise2 = promise.then(readAnotherFile, console.error)
複製代碼
這裏 then 的參數 readAnotherFile, console.error 是表明異步操做成功後的動做 onFulfilled 或失敗後的動做 OnRejected,也就是說,讀取文件成功後執行 readAnotherFile 函數,不然失敗打印記錄錯誤。這種實現是兩個中只有一種可能 也能夠理解爲:webpack
promiseSomething().then(function (fulfilled) {
// 當 promise 狀態變成 fulfilled 時,調用此函數
}, function (rejected) {
// 當 promise 狀態變成 rejected 時,調用此函數
}, function (progress) {
// 當返回進度信息時,調用此函數
});//歡迎加入全棧開發交流圈一塊兒學習交流:864305860
複製代碼
Promise 法則有兩部分必須分離:web
來看一個利用 q 來處理這種問題的簡單例子:面試
var Q = require('q');
var defer = Q.defer();
/**
* 獲取初始 promise
* @private
*/
function getInitialPromise() {
return defer.promise;
}//歡迎加入全棧開發交流圈一塊兒學習交流:864305860
/**
* 爲 promise 設置三種狀態的回調函數
*/
getInitialPromise().then(function (success) {
console.log(success);
}, function (error) {
console.log(error);
}, function (progress) {
console.log(progress);
});
defer.notify('in progress'); // 控制檯打印 in progress
defer.resolve('resolve'); // 控制檯打印 resolve
defer.reject('reject'); // 沒有輸出。promise 的狀態只能改變一次
複製代碼
promise 的傳遞 then 方法會返回一個 promise,在下面這個例子中,咱們用 outputPromise 指向 then 返回的 promise。數據庫
var outputPromise = getInputPromise().then(function (fulfilled) {
}, function (rejected) {
});
複製代碼
如今 outputPromise 就變成了受 function(fulfilled) 或者 function(rejected) 控制狀態的 promise 了。直白的意思就是:當 function(fulfilled) 或者 function(rejected) 返回一個值,好比一個字符串,數組,對象等等,那麼 outputPromise 的狀態就會變成 fulfilled。 在下面這個例子中,咱們能夠看到,當咱們把 inputPromise 的狀態經過 defer.resovle() 變成 fulfilled 時,控制檯輸出 fulfilled. 當咱們把 inputPromise 的狀態經過 defer.reject() 變成 rejected,控制檯輸出 rejected
var Q = require('q');
var defer = Q.defer();
/**//歡迎加入全棧開發交流圈一塊兒學習交流:864305860
* 經過 defer 得到 promise
* @private
*/
function getInputPromise() {
return defer.promise;
}
/**
* 當 inputPromise 狀態由未完成變成 fulfil 時,調用 function(fulfilled)
* 當 inputPromise 狀態由未完成變成 rejected 時,調用 function(rejected)
* 將 then 返回的 promise 賦給 outputPromise
* function(fulfilled) 和 function(rejected) 經過返回字符串將 outputPromise 的狀態由
* 未完成改變爲 fulfilled
* @private
*///歡迎加入全棧開發交流圈一塊兒學習交流:864305860
var outputPromise = getInputPromise().then(function (fulfilled) {
return 'fulfilled';
}, function (rejected) {
return 'rejected';
});
/**
* 當 outputPromise 狀態由未完成變成 fulfil 時,調用 function(fulfilled),控制檯打印 'fulfilled: fulfilled'。
* 當 outputPromise 狀態由未完成變成 rejected, 調用 function(rejected), 控制檯打印 'rejected: rejected'。
*/
outputPromise.then(function (fulfilled) {
console.log('fulfilled: ' + fulfilled);
}, function (rejected) {
console.log('rejected: ' + rejected);
});
/**//歡迎加入全棧開發交流圈一塊兒學習交流:864305860
* 將 inputPromise 的狀態由未完成變成 rejected
*/
defer.reject(); // 輸出 fulfilled: rejected
/**
* 將 inputPromise 的狀態由未完成變成 fulfilled
*/
//defer.resolve(); // 輸出 fulfilled: fulfilled
複製代碼
當 function(fulfilled) 或者 function(rejected) 拋出異常時,那麼 outputPromise 的狀態就會變成 rejected
var Q = require('q');
var fs = require('fs');
var defer = Q.defer();
/**
* 經過 defer 得到 promise
* @private
*///歡迎加入全棧開發交流圈一塊兒學習交流:864305860
function getInputPromise() {
return defer.promise;
}
/**
* 當 inputPromise 狀態由未完成變成 fulfil 時,調用 function(fulfilled)
* 當 inputPromise 狀態由未完成變成 rejected 時,調用 function(rejected)
* 將 then 返回的 promise 賦給 outputPromise
* function(fulfilled) 和 function(rejected) 經過拋出異常將 outputPromise 的狀態由
* 未完成改變爲 reject
* @private
*/
var outputPromise = getInputPromise().then(function (fulfilled) {
throw new Error('fulfilled');
}, function (rejected) {
throw new Error('rejected');
});
/**
* 當 outputPromise 狀態由未完成變成 fulfil 時,調用 function(fulfilled)。
* 當 outputPromise 狀態由未完成變成 rejected, 調用 function(rejected)。
*/
outputPromise.then(function (fulfilled) {
console.log('fulfilled: ' + fulfilled);
}, function (rejected) {
console.log('rejected: ' + rejected);
});
/**
* 將 inputPromise 的狀態由未完成變成 rejected
*/
defer.reject(); // 控制檯打印 rejected [Error:rejected]
//歡迎加入全棧開發交流圈一塊兒學習交流:864305860
/**
* 將 inputPromise 的狀態由未完成變成 fulfilled
*/
//defer.resolve(); // 控制檯打印 rejected [Error:fulfilled]
複製代碼
當 function(fulfilled) 或者 function(rejected) 返回一個 promise 時,outputPromise 就會成爲這個新的 promise. 這樣作的意義在於聚合結果 (Q.all),管理延時,異常恢復等等 好比說咱們想要讀取一個文件的內容,而後把這些內容打印出來。可能會寫出這樣的代碼:
// 錯誤的寫法
var outputPromise = getInputPromise().then(function (fulfilled) {
fs.readFile('test.txt', 'utf8', function (err, data) {
return data;
});//歡迎加入全棧開發交流圈一塊兒學習交流:864305860
});
複製代碼
然而這樣寫是錯誤的,由於 function(fulfilled) 並無返回任何值。須要下面的方式:
var Q = require('q');
var fs = require('fs');
var defer = Q.defer();
/**
* 經過 defer 得到promise
* @private
*///歡迎加入全棧開發交流圈一塊兒學習交流:864305860
function getInputPromise() {
return defer.promise;
}
/**
* 當 inputPromise 狀態由未完成變成 fulfil時,調用 function(fulfilled)
* 當 inputPromise 狀態由未完成變成 rejected時,調用 function(rejected)
* 將 then 返回的 promise 賦給 outputPromise
* function(fulfilled) 將新的 promise 賦給 outputPromise
* 未完成改變爲 reject
* @private
*///歡迎加入全棧開發交流圈一塊兒學習交流:864305860
var outputPromise = getInputPromise().then(function (fulfilled) {
var myDefer = Q.defer();
fs.readFile('test.txt', 'utf8', function (err, data) {
if (!err && data) {
myDefer.resolve(data);
}
});//歡迎加入全棧開發交流圈一塊兒學習交流:864305860
return myDefer.promise;
}, function (rejected) {
throw new Error('rejected');
});
/**
* 當 outputPromise 狀態由未完成變成 fulfil 時,調用 function(fulfilled),控制檯打印 test.txt 文件內容。
*
*///歡迎加入全棧開發交流圈一塊兒學習交流:864305860
outputPromise.then(function (fulfilled) {
console.log(fulfilled);
}, function (rejected) {
console.log(rejected);
});
/**
* 將 inputPromise 的狀態由未完成變成 rejected
*/
//defer.reject();
/**
* 將 inputPromise 的狀態由未完成變成 fulfilled
*/
defer.resolve(); // 控制檯打印出 test.txt 的內容
複製代碼
方法傳遞 方法傳遞有些相似於 Java 中的 try 和 catch。當一個異常沒有響應的捕獲時,這個異常會接着往下傳遞 方法傳遞的含義是當一個狀態沒有響應的回調函數,就會沿着 then 往下找 沒有提供 function(rejected)
var outputPromise = getInputPromise().then(function (fulfilled) { })
複製代碼
若是 inputPromise 的狀態由未完成變成 rejected, 此時對 rejected 的處理會由 outputPromise 來完成
var Q = require('q');
var fs = require('fs');
var defer = Q.defer();
/**
* 經過defer得到promise
* @private
*///歡迎加入全棧開發交流圈一塊兒學習交流:864305860
function getInputPromise() {
return defer.promise;
}
/**
* 當 inputPromise 狀態由未完成變成 fulfil 時,調用 function(fulfilled)
* 當 inputPromise 狀態由未完成變成 rejected 時,這個 rejected 會傳向 outputPromise
*/
var outputPromise = getInputPromise().then(function (fulfilled) {
return 'fulfilled'
});//歡迎加入全棧開發交流圈一塊兒學習交流:864305860
outputPromise.then(function (fulfilled) {
console.log('fulfilled: ' + fulfilled);
}, function (rejected) {
console.log('rejected: ' + rejected);
});
/**
* 將 inputPromise 的狀態由未完成變成 rejected
*/
defer.reject('inputpromise rejected'); // 控制檯打印 rejected: inputpromise rejected
/**
* 將 inputPromise的狀態由未完成變成fulfilled
*///歡迎加入全棧開發交流圈一塊兒學習交流:864305860
//defer.resolve();
複製代碼
沒有提供 function(fulfilled)
var outputPromise = getInputPromise().then(null, function (rejected) { })
複製代碼
若是 inputPromise 的狀態由未完成變成 fulfilled, 此時對 fulfil 的處理會由 outputPromise 來完成
var Q = require('q');
var fs = require('fs');
var defer = Q.defer();
/**
* 經過defer得到promise
* @private
*/
function getInputPromise() {
return defer.promise;
}//歡迎加入全棧開發交流圈一塊兒學習交流:864305860
/**
* 當 inputPromise 狀態由未完成變成 fulfil時,傳遞給 outputPromise
* 當 inputPromise 狀態由未完成變成 rejected時,調用 function(rejected)
* function(fulfilled) 將新的 promise 賦給 outputPromise
* 未完成改變爲 reject
* @private
*/
var outputPromise = getInputPromise().then(null, function (rejected) {
return 'rejected';
}); //歡迎加入全棧開發交流圈一塊兒學習交流:864305860
outputPromise.then(function (fulfilled) {
console.log('fulfilled: ' + fulfilled);
}, function (rejected) {
console.log('rejected: ' + rejected);
});
/**
* 將 inputPromise 的狀態由未完成變成 rejected
*/
// defer.reject('inputpromise rejected');
/**
* 將 inputPromise 的狀態由未完成變成fulfilled
*/
defer.resolve('inputpromise fulfilled'); // 控制檯打印fulfilled: inputpromise fulfilled
複製代碼
可使用 fail(function(error)) 來專門針對錯誤處理,而不是使用 then(null,function(error))
var outputPromise = getInputPromise().fail(function (error) { })
複製代碼
看這個例子:
var Q = require('q');
var fs = require('fs');
var defer = Q.defer();
/**//歡迎加入全棧開發交流圈一塊兒學習交流:864305860
* 經過defer得到promise
* @private
*/
function getInputPromise() {
return defer.promise;
}
/**
* 當 inputPromise 狀態由未完成變成 fulfil 時,調用 then(function(fulfilled))
* 當 inputPromise 狀態由未完成變成 rejected 時,調用 fail(function(error))
* function(fulfilled) 將新的 promise 賦給 outputPromise
* 未完成改變爲reject
* @private
*/
var outputPromise = getInputPromise().then(function (fulfilled) {
return fulfilled;
}).fail(function (error) {
console.log('fail: ' + error);
});//歡迎加入全棧開發交流圈一塊兒學習交流:864305860
/**
* 將 inputPromise 的狀態由未完成變成 rejected
*/
defer.reject('inputpromise rejected');// 控制檯打印 fail: inputpromise rejected
/**
* 將 inputPromise 的狀態由未完成變成 fulfilled
*/
//defer.resolve('inputpromise fulfilled');
複製代碼
可使用 progress(function (progress)) 來專門針對進度信息進行處理,而不是使用 then(function (success) { }, function (error) { }, function (progress) { })
var Q = require('q');
var defer = Q.defer();
/**
* 獲取初始 promise
* @private
*/
function getInitialPromise() {
return defer.promise;
}
/**//歡迎加入全棧開發交流圈一塊兒學習交流:864305860
* 爲 promise 設置 progress 信息處理函數
*/
var outputPromise = getInitialPromise().then(function (success) {
}).progress(function (progress) {
console.log(progress);
});
defer.notify(1);
defer.notify(2); // 控制檯打印 1,2
複製代碼
promise 鏈 promise 鏈提供了一種讓函數順序執行的方法 函數順序執行是很重要的一個功能。好比知道用戶名,須要根據用戶名從數據庫中找到相應的用戶,而後將用戶信息傳給下一個函數進行處理
var Q = require('q');
var defer = Q.defer();
// 一個模擬數據庫
var users = [{ 'name': 'andrew', 'passwd': 'password' }];
function getUsername() {
return defer.promise;
}
function getUser(username) {
var user;
users.forEach(function (element) {
if (element.name === username) {
user = element;
}
});//歡迎加入全棧開發交流圈一塊兒學習交流:864305860
return user;
}
// promise 鏈
getUsername().then(function (username) {
return getUser(username);
}).then(function (user) {
console.log(user);
});
defer.resolve('andrew');
複製代碼
咱們經過兩個 then 達到讓函數順序執行的目的。 then 的數量實際上是沒有限制的。固然,then 的數量過多,要手動把他們連接起來是很麻煩的。好比
foo(initialVal).then(bar).then(baz).then(qux)
複製代碼
這時咱們須要用代碼來動態製造 promise 鏈
var funcs = [foo, bar, baz, qux]
var result = Q(initialVal)
funcs.forEach(function (func) {
result = result.then(func)
})
return result
複製代碼
固然,咱們能夠再簡潔一點
var funcs = [foo, bar, baz, qux]
funcs.reduce(function (pre, current),Q(initialVal){
return pre.then(current)
})//歡迎加入全棧開發交流圈一塊兒學習交流:864305860
複製代碼
看一個具體的例子
function foo(result) {
console.log(result);
return result + result;
}
// 手動連接
Q('hello').then(foo).then(foo).then(foo);
// 控制檯輸出: hello
// hellohello
// hellohellohello
// 動態連接
var funcs = [foo, foo, foo];
var result = Q('hello');
funcs.forEach(function (func) {
result = result.then(func);
});
//歡迎加入全棧開發交流圈一塊兒學習交流:864305860
// 精簡後的動態連接
funcs.reduce(function (prev, current) {
return prev.then(current);
}, Q('hello'));
複製代碼
對於 promise 鏈,最重要的是須要理解爲何這個鏈可以順序執行。若是可以理解這點,那麼之後本身寫 promise 鏈能夠說是輕車熟路啊 promise 組合 回到咱們一開始讀取文件內容的例子。若是如今讓咱們把它改寫成 promise 鏈,是否是很簡單呢?
var Q = require('q'),
fs = require('fs');
function printFileContent(fileName) {
return function () {
var defer = Q.defer();
fs.readFile(fileName, 'utf8', function (err, data) {
if (!err && data) {
console.log(data);
defer.resolve();
}
})
return defer.promise;
}
} //歡迎加入全棧開發交流圈一塊兒學習交流:864305860
// 手動連接
printFileContent('sample01.txt')()
.then(printFileContent('sample02.txt'))
.then(printFileContent('sample03.txt'))
.then(printFileContent('sample04.txt')); // 控制檯順序打印 sample01 到 sample04 的內容
複製代碼
咱們會發現爲何要他們順序執行呢,若是他們可以並行執行不是更好嗎? 咱們只須要在他們都執行完成以後,獲得他們的執行結果就能夠了 咱們能夠經過 Q.all([promise1,promise2...]) 將多個 promise 組合成一個 promise 返回。 注意:
咱們來把上面讀取文件內容的例子改爲並行執行吧
var Q = require('q');
var fs = require('fs');
/**//歡迎加入全棧開發交流圈一塊兒學習交流:864305860
*讀取文件內容
*@private
*/
function printFileContent(fileName) {
// Todo: 這段代碼不夠簡潔。可使用 Q.denodeify 來簡化
var defer = Q.defer();
fs.readFile(fileName, 'utf8', function (err, data) {
if (!err && data) {
console.log(data);
defer.resolve(fileName + ' success ');
} else {
defer.reject(fileName + ' fail ');
}
}) //歡迎加入全棧開發交流圈一塊兒學習交流:864305860
return defer.promise;
}
Q.all([printFileContent('sample01.txt'), printFileContent('sample02.txt'), printFileContent('sample03.txt'), printFileContent('sample04.txt')])
.then(function (success) {
console.log(success);
}); // 控制檯打印各個文件內容 順序不必定
複製代碼
如今知道 Q.all 會在任意一個 promise 進入 reject 狀態後當即進入 reject 狀態。若是咱們須要等到全部的 promise 都發生狀態後(有的 fulfil, 有的 reject),再轉換 Q.all 的狀態, 這時咱們可使用 Q.allSettled
var Q = require('q'),
fs = require('fs');
/**
*讀取文件內容
*@private
*/
function printFileContent(fileName) {
// Todo: 這段代碼不夠簡潔。可使用Q.denodeify來簡化
var defer = Q.defer();
fs.readFile(fileName, 'utf8', function (err, data) {
if (!err && data) {
console.log(data);
defer.resolve(fileName + ' success ');
} else {
defer.reject(fileName + ' fail ');
}//歡迎加入全棧開發交流圈一塊兒學習交流:864305860
})
return defer.promise;
}
Q.allSettled([printFileContent('nosuchfile.txt'),
printFileContent('sample02.txt'),
printFileContent('sample03.txt'),
printFileContent('sample04.txt')])
.then(function (results) {
results.forEach(
function (result) {
console.log(result.state);
}
);
});//歡迎加入全棧開發交流圈一塊兒學習交流:864305860
複製代碼
結束 promise 鏈 一般,對於一個 promise 鏈,有兩種結束的方式。第一種方式是返回最後一個 promise 如 return foo().then(bar); 第二種方式就是經過 done 來結束 promise 鏈 如 foo().then(bar).done() 爲何須要經過 done 來結束一個 promise 鏈呢? 若是在咱們的鏈中有錯誤沒有被處理,那麼在一個正確結束的 promise 鏈中,這個沒被處理的錯誤會經過異常拋出
var Q = require('q');
function getPromise(msg, timeout, opt) {
var defer = Q.defer();
setTimeout(function () {
console.log(msg);
if (opt)
defer.reject(msg);
else
defer.resolve(msg);
}, timeout);
return defer.promise;
} //歡迎加入全棧開發交流圈一塊兒學習交流:864305860
/**
* 沒有用 done() 結束的 promise 鏈
* 因爲 getPromse('2',2000,'opt') 返回 rejected, getPromise('3',1000) 就沒有執行
* 而後這個異常並無任何提醒,是一個潛在的 bug
*/
getPromise('1', 3000)
.then(function () { return getPromise('2', 2000, 'opt') })
.then(function () { return getPromise('3', 1000) });
/**
* 用 done() 結束的 promise 鏈
* 有異常拋出
*/
getPromise('1', 3000)
.then(function () { return getPromise('2', 2000, 'opt') })
.then(function () { return getPromise('3', 1000) })
.done();
複製代碼
promise 表明一個異步操做的最終結果。主要經過 promise 的 then 方法訂閱其最終結果的處理回調函數,和訂閱因某緣由沒法成功獲取最終結果的處理回調函數。
A 與 A+ 的不一樣點
結語
感謝您的觀看,若有不足之處,歡迎批評指正。
本次給你們推薦一個免費的學習羣,裏面歸納移動應用網站開發,css,html,webpack,vue node angular以及面試資源等。 對web開發技術感興趣的同窗,歡迎加入Q羣:864305860,無論你是小白仍是大牛我都歡迎,還有大牛整理的一套高效率學習路線和教程與您免費分享,同時天天更新視頻資料。 最後,祝你們早日學有所成,拿到滿意offer,快速升職加薪,走上人生巔峯。