JS語言的執行環境是「單線程」的,即指一次只能完成一件任務;若是有多個任務,那麼必須排隊,前面一個任務完成,再執行後一個任務,以此類推。這種模式的好處是實現起來比較簡單,執行環境相對單純;壞處是隻要有一個任務耗時很長,後面的任務都必須排隊等着,會拖延整個程序的執行。常見的瀏覽器無響應(假死),每每就是由於某一段Javascript代碼長時間運行(好比死循環),致使整個頁面卡在這個地方,其餘任務沒法執行。node
爲了解決這個問題,Javascript語言將任務的執行模式分紅兩種:同步(Synchronous)和異步(Asynchronous)。git
"同步模式"就是上一段的模式,後一個任務等待前一個任務結束,而後再執行,程序的執行順序與任務的排列順序是一致的、同步的;"異步模式"則徹底不一樣,每個任務有一個或多個回調函數(callback),前一個任務結束後,不是執行後一個任務,而是執行回調函數,後一個任務則是不等前一個任務結束就執行,因此程序的執行順序與任務的排列順序是不一致的、異步的。es6
"異步模式"很是重要。在瀏覽器端,耗時很長的操做都應該異步執行,避免瀏覽器失去響應,最好的例子就是Ajax操做。在服務器端,"異步模式"甚至是惟一的模式,由於執行環境是單線程的,若是容許同步執行全部http請求,服務器性能會急劇降低,很快就會失去響應。github
本文着重講ES6的Promise對象的定義和用法 阮一峯老師的ES6詳解 - Promise對象 相信你們在學習ES6的過程當中都或多或少的學習過阮老師的ES6教程,那麼這裏簡單舉一些例子講述Promise對象的特色和使用方法npm
ES6提供Promise構造函數,咱們創造一個Promise實例,Promise構造函數接收一個函數做爲參數,這個傳入的函數有兩個參數,分別是兩個函數 resolve
和reject
做用是,resolve
將Promise的狀態由未成功變爲成功,將異步操做的結果做爲參數傳遞過去;類似的是reject
則將狀態由未失敗轉變爲失敗,在異步操做失敗時調用,將異步操做報出的錯誤做爲參數傳遞過去。
實例建立完成後,可使用then
方法分別指定成功或失敗的回調函數,比起f1(f2(f3))的層層嵌套的回調函數寫法,鏈式調用的寫法更爲美觀易讀編程
let promise = new Promise((resolve, reject)=>{
reject("拒絕了");
});
promise.then((data)=>{
console.log('success' + data);
}, (error)=>{
console.log(error)
});
執行結果:"拒絕了"
複製代碼
let promise = new Promise((resolve, reject)=>{
reject("拒絕了");
resolve("又經過了");
});
promise.then((data)=>{
console.log('success' + data);
}, (error)=>{
console.log(error)
});
執行結果: "拒絕了"
複製代碼
上述代碼不會再執行resolve的方法數組
then
方法下一次的輸入須要上一次的輸出then
中then
中返回的不是Promise對象而是一個普通值,則會將這個結果做爲下次then的成功的結果then
中失敗了 會走下一個then
的失敗then
中不寫方法則值會穿透,傳入下一個then
中用node fs模塊讀取文件的流程來測試 咱們建立一個讀取文件的方法,在Promise中定義若是讀取成功則展現文件的內容,不然報出錯誤promise
let fs = require('fs');
function read(file, encoding) {
return new Promise((resolve, reject)=>{
fs.readFile(filePath, encodeing, (err, data)=> {
if (err) reject(err);
resolve(data);
});
})
}
複製代碼
因爲想看到屢次連貫回調,咱們專門設置3個txt文件,其中1號文件的內容爲2號文件的文件名,2號文件的內容爲3號文件的文件名,3號中展現最終內容瀏覽器
執行代碼以下:緩存
read('1.promise/readme.txt', 'utf8').then((data)=>{
console.log(data)
});
複製代碼
讀取一個文件的打印結果爲,readme2.txt
咱們改造這個代碼,添加多個回調,在最後一個以前的全部then中都return出當前返回的promise對象
read('readme.txt', 'utf8').then((data)=>{
return read(data, 'utf8');
}).then((data)=>{
return read(data, 'utf8')
}).then((data)=>{
console.log(data);
});
最終輸出 readme3.txt的內容
複製代碼
再對下一步then進行新的處理,咱們對readme3.txt的內容進行加工並返回
read('readme.txt', 'utf8').then((data)=>{
return read(data, 'utf8');
}).then((data)=>{
return read(data, 'utf8')
}).then(data=>{
return data.split('').reverse().join(); // 這一步返回的是一個普通值,普通值在下一個then會做爲resolve處理
}).then(null,data=>{ // 特地不對成功作處理,放過這一個值,進入下一步
throw new Error('出錯') // 因爲上一步返回的是普通值,走成功回調,不會走到這裏
}).then(data=>{
console.log(data) // 最終會打印出來上上步的普通值
});
複製代碼
這裏咱們將內容處理後,則將一個普通值傳給了下次的then,可是因爲下一個then沒有處理成功方法(null)
這個普通值會繼續傳入下一個then,最終會做爲成功值打印出來。最後咱們看一下對錯誤的處理,在處理完readme3.txt的結果後,咱們將這個值傳入下一個then中,令其做爲一個文件名打開,然而此時已經找不到這個不存在的文件了,那麼在最後一步就會打印出報錯的結果
read('readme.txt', 'utf8').then((data)=>{
return read(data, 'utf8');
}).then((data)=>{
return read(data, 'utf8')
}).then(data=>{
return data.split('').reverse().join();
}).then(null,data=>{
throw new Error('出錯')
}).then(data=>{
return read(data, 'utf8')
}).then(null,(err)=>{
console.log(err)
});
複製代碼
結果:
Promises Aplus規範即規定了Promise的原理,源代碼規範等,經過這個規範,咱們能夠本身實現一個基於PromiseA+規範的Promise類庫,下面咱們展現一下源碼的實現
/**
* Promise 實現 遵循promise/A+規範
* 官方站: https://promisesaplus.com/
* Promise/A+規範譯文:
* https://malcolmyu.github.io/2015/06/12/Promises-A-Plus/#note-4
*/
// promise 三個狀態
const PENDING = "pending";
const FULFILLED = "fulfilled";
const REJECTED = "rejected";
function Promise(excutor) {
let self = this; // 緩存當前promise實例對象
self.status = PENDING; // 初始狀態
self.value = undefined; // fulfilled狀態時 返回的信息
self.reason = undefined; // rejected狀態時 拒絕的緣由
self.onFulfilledCallbacks = []; // 存儲fulfilled狀態對應的onFulfilled函數
self.onRejectedCallbacks = []; // 存儲rejected狀態對應的onRejected函數
function resolve(value) { // value成功態時接收的終值
if(value instanceof Promise) {
return value.then(resolve, reject);
}
// 爲何resolve 加setTimeout?
// 2.2.4規範 onFulfilled 和 onRejected 只容許在 execution context 棧僅包含平臺代碼時運行.
// 這裏的平臺代碼指的是引擎、環境以及 promise 的實施代碼。實踐中要確保 onFulfilled 和 onRejected 方法異步執行,且應該在 then 方法被調用的那一輪事件循環以後的新執行棧中執行。
setTimeout(() => {
// 調用resolve 回調對應onFulfilled函數
if (self.status === PENDING) {
// 只能由pedning狀態 => fulfilled狀態 (避免調用屢次resolve reject)
self.status = FULFILLED;
self.value = value;
self.onFulfilledCallbacks.forEach(cb => cb(self.value));
}
});
}
function reject(reason) { // reason爲失敗態時接收的緣由
setTimeout(() => {
// 調用reject 回調對應onRejected函數
if (self.status === PENDING) {
// 只能由pedning狀態 => rejected狀態 (避免調用屢次resolve reject)
self.status = REJECTED;
self.reason = reason;
self.onRejectedCallbacks.forEach(cb => cb(self.reason));
}
});
}
// 捕獲在excutor執行器中拋出的異常
// new Promise((resolve, reject) => {
// throw new Error('error in excutor')
// })
try {
excutor(resolve, reject);
} catch (e) {
reject(e);
}
}
複製代碼
這一部分代碼咱們對resolve和reject進行了判斷處理,接着咱們構造then
方法
/**
* [註冊fulfilled狀態/rejected狀態對應的回調函數]
* @param {function} onFulfilled fulfilled狀態時 執行的函數
* @param {function} onRejected rejected狀態時 執行的函數
* @return {function} promise2 返回一個新的promise對象
*/
Promise.prototype.then = function (onFulfilled, onRejected) {
// 成功和失敗的回調 是可選參數
// onFulfilled成功的回調 onRejected失敗的回調
let self = this;
let promise2;
// 須要每次調用then時都返回一個新的promise
promise2 = new Promise((resolve, reject) => {
// 成功態
if (self.status === 'resolved') {
setTimeout(()=>{
try {
// 當執行成功回調的時候 可能會出現異常,那就用這個異常做爲promise2的錯誤的結果
let x = onFulfilled(self.value);
//執行完當前成功回調後返回結果多是promise
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') {
// 等待態時,當一部調用resolve/reject時,將onFullfilled/onReject收集暫存到集合中
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
}
// 其中規範要求對回調中增長setTimeout處理
複製代碼
能夠看到resolve和reject都有一個處理新promise的方法resolvePromise,對其進行封裝,達處處理不一樣狀況的目的
/**
* 對resolve 進行改造加強 針對resolve中不一樣值狀況 進行處理
* @param {promise} promise2 promise1.then方法返回的新的promise對象
* @param {[type]} x promise1中onFulfilled的返回值
* @param {[type]} resolve promise2的resolve方法
* @param {[type]} reject promise2的reject方法
*/
function resolvePromise(promise2,x,resolve,reject){
if(promise2 === x){ // 若是從onFullfilled中返回的x就是promise2,就會致使循環引用報錯
return reject(new TypeError('Chaining cycle'));
}
let called; // 聲明避免屢次使用
// x類型判斷 若是是對象或者函數
if(x!==null && (typeof x=== 'object' || typeof x === 'function')){
// 判斷是不是thenable對象
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);
}
}
複製代碼
以上基本實現了Promise的基本方法,根據Promise的用法,補充一些類上的方法
// 用於promise方法鏈時 捕獲前面onFulfilled/onRejected拋出的異常
Promise.reject = function(reason){
return new Promise((resolve,reject)=>{
reject(reason);
})
}
Promise.resolve = function(value){
return new Promise((resolve,reject)=>{
resolve(value);
})
}
Promise.prototype.catch = function(onRejected){
// 默認不寫成功
return this.then(null,onRejected);
};
/**
* Promise.all Promise進行並行處理
* 參數: promise對象組成的數組做爲參數
* 返回值: 返回一個Promise實例
* 當這個數組裏的全部promise對象所有變爲resolve狀態的時候,纔會resolve。
*/
Promise.all = function(promises){
return new Promise((resolve,reject)=>{
let arr = [];
let i = 0;
function processData(index,data){
arr[index] = data;
if(++i == promises.length){
resolve(arr);
}
}
for(let i = 0;i<promises.length;i++){
promises[i].then(data=>{ // data是成功的結果
processData(i,data);
},reject);
}
})
}
/**
* Promise.race
* 參數: 接收 promise對象組成的數組做爲參數
* 返回值: 返回一個Promise實例
* 只要有一個promise對象進入 FulFilled 或者 Rejected 狀態的話,就會繼續進行後面的處理(取決於哪個更快)
*/
Promise.race = function(promises){
return new Promise((resolve,reject)=>{
for(let i = 0;i<promises.length;i++){
promises[i].then(resolve,reject);
}
})
}
複製代碼
最後咱們導出方法
module.exports = Promise;
複製代碼
至此一個符合PromiseA+規範的本身寫的源碼庫完成了,能夠測試使用這個庫替代Promise,以測試是否有邏輯錯誤等,或者可使用
npm install promises-aplus-tests -g
promises-aplus-test 文件名
複製代碼
插件來測試該源碼是否符合PromiseA+規範
但願這篇文章能幫到你,以上