希沃ENOW大前端javascript
公司官網:CVTE(廣州視源股份)前端
團隊:CVTE旗下將來教育希沃軟件平臺中心enow團隊java
本文做者:es6
Promise 是一種異步編程的解決方案,能夠認爲它是一個容器,裏面保存着將來發生的事件結果。 它有三種狀態:pending
(進行中)、fulfilled
(已成功)和rejected
(已失敗),狀態一旦發生改變就不能再次改變。編程
在處理異步請求時,咱們通常都會使用回調函數這麼處理,這麼看徹底沒毛病,邏輯清晰。promise
http.post(data,function(res) {
// do something here
})
複製代碼
可是若是咱們須要根據前一個請求返回的結果來發起下一個請求的話,代碼則變成了這樣:markdown
http.post(data,function(res1) {
http.post(res1,function(res2) {
// do something here
})
})
複製代碼
隨着產品和業務邏輯逐漸複雜,可能就會滋生出這種代碼:異步
http.post(data,function(res1){
http.post(res1,function(res2){
http.post(res2,function(res3){
http.post(res3,function(res4){
http.post(res4,function(res5){
http.post(res5,function(res6){
// do something here
})
})
})
})
})
})
複製代碼
這即是臭名昭著的回調地獄了,帶來的負面影響也是不言而喻的:async
若是使用 Promise 咱們能夠寫成這樣:編程語言
fetch(data).then(res1 => {
return fetch(res1);
}).then(res2 => {
return fetch(res2);
}).then(res3 => {
return fetch(res3);
}).catch(err => {
console.error('err: ', err);
})
複製代碼
Promise
的鏈式調用可讓代碼變得更加直觀,雖然相對看起來邏輯清晰點,但依然仍是存在then
調用鏈,有代碼冗餘的問題,還存在如下的不足:
const promise = new Promise((resolve, reject) => {
console.log(1);
resolve();
setTimeout(() => {
console.log(2);
})
reject('error');
})
promise.then(() => {
console.log(3);
}).then(() => {
console.log(5)
}).catch(e => console.log(e))
console.log(4);
複製代碼
老生常談的話題,也就是考察宏任務和微任務。重點主要是:
正確的輸出順序是一、四、三、五、2
這裏簡單的實現一個可以知足then
方法鏈式調用的Promise
:
class Promise {
constructor(params) {
//初始化state爲pending
this.state = 'pending';
//成功的值,返回通常都是undefined
this.value = undefined;
//失敗的緣由,返回通常都是undefined
this.reason = undefined;
//成功執行函數隊列
this.onResolvedCallbacks = [];
//失敗執行函數隊列
this.onRejectedCallbacks = [];
//success
let resolve = value => {
if (this.state === 'pending') {
//state change
this.state = 'fulfilled';
//儲存成功的值
this.value = value;
//一旦成功,調用函數隊列
this.onResolvedCallbacks.forEach(fn => fn());
}
};
//error
let reject = reason => {
if (this.state === 'pending') {
//state change
this.state = 'rejected';
//儲存成功的緣由
this.reason = reason;
//一旦失敗,調用函數隊列
this.onRejectedCallbacks.forEach(fn => fn());
}
};
try {
params(resolve, reject);
} catch (err) {
reject(err);
}
}
then(onFulfilled, onRejected) {
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
onRejected = typeof onRejected === 'function' ? onRejected : err => {
throw err
};
let promise2 = new Promise((resolve, reject) => {
//當狀態是fulfilled時執行onFulfilled函數
if (this.state === 'fulfilled') {
//異步實現
setTimeout(() => {
try {
let x = onFulfilled(this.value);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
}, 0);
};
//當狀態是rejected時執行onRejected函數
if (this.state === 'rejected') {
//異步實現
setTimeout(() => {
try {
let x = onRejected(this.reason);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
}, 0);
};
//當狀態是pending時,往onFulfilledCacks、onRejectedCacks里加入函數
if (this.state === 'pending') {
this.onResolvedCallbacks.push(() => {
//異步實現
setTimeout(() => {
try {
let x = onFulfilled(this.value);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
}, 0);
});
this.onRejectedCallbacks.push(() => {
//異步實現
setTimeout(() => {
try {
let x = onRejected(this.reason);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
}, 0)
});
};
});
return promise2;
}
catch(fn) {
return this.then(null, fn);
}
}
function resolvePromise(promise2, x, resolve, reject) {
//循環引用報錯
if (x === promise2) {
return reject(new TypeError('Chaining cycle detected for promise'));
}
//防止屢次調用
let called;
//判斷x
if (x != null && (typeof x === 'object' || typeof x === 'function')) {
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);
}
}
//resolve方法
Promise.resolve = function (val) {
return new Promise((resolve, reject) => {
resolve(val)
});
}
//reject方法
Promise.reject = function (val) {
return new Promise((resolve, reject) => {
reject(val);
});
}
const test = new Promise((res, rej) => {
setTimeout(() => {
res('resolve after 2000ms');
}, 2000)
})
test.then(res => {
console.error('res: ', res); // res: resolve after 2000ms
})
複製代碼
在這裏咱們的回調函數用setTimeout
實現,把它們放到了宏任務隊列裏,那有什麼辦法能夠把它放到微任務隊列裏嗎?該怎麼作?有興趣的童鞋能夠嘗試一下實現一個符合Promise/A+規範的 Promise
。
還能夠用 ES6 中的 Generator 來處理,Generator 的執行有點相似於傳統編程語言的協程。協程的執行步驟大體如:
Javascript 中的異步任務就相似於上述的協程A,分紅兩段(或多段)執行。
Generator 和 Promise 相似,均可以認爲是一個容器,不一樣之處在於 Generator 的容器是用來裝異步任務的而不是狀態。在須要異步操做的地方,使用 yield
交出控制權便可,使用next
方法則能夠奪回控制權,恢復執行,且next
方法的參數能夠做爲上一個yield
表達式的返回值。
仍是同一個例子,咱們用 Generator 來實現一波:
function* getData(data) {
const res1 = yield http.post(data);
const res2 = yield http.post(res1);
const res3 = yield http.post(res2);
const res4 = yield http.post(res3);
return http.post(res4);
}
const g = getData(123);
const res1 = g.next(); // {value: res1,done: false}
const res2 = g.next(res1.value); // {value: res2,done: false}
const res3 = g.next(res2.value); // {value: res3,done: false}
const res4 = g.next(res3.value); // {value: res4,done: false}
const res5 = g.next(res4.value); // {value: res5,done: true}
const res6 = g.next() // {value: undefined,done: true}
複製代碼
當調用getData
時,並不會返回結果,而是返回了一個指針對象g
。指針對象g
上的next
方法,可讓內部指針指向下一個yield
語句,並返回一個表示當前執行狀態的對象。value
屬性是當前yield
表達式的值,done
屬性表示當前的遍歷是否結束。當遍歷結束後,若是繼續調用next
方法,則會返回undefined
。
此外,Generator 還能夠提早被終止,只須要調用指針對象上的return
方法便可,返回對象上的done
屬性爲true
,以後再次調用next
方法,老是返回done
爲true
。
function* getData(data) {
yield 1;
yield 2;
yield 3;
}
const g = getData();
g.next(); // {value: 1, done: false}
g.return('done'); // {value: 'done', done: true}
g.next(); // {value: undefined, done: true}
複製代碼
對於錯誤捕獲, Generator 能夠在外部捕獲錯誤
function* getData(data) {
try{
const res = yield data;
} catch(error) {
console.error('inner catch error: ', error);
}
}
const g = getData(123);
g.next();
try {
g.throw('err1'); // inner catch error: throw err
g.throw('err2') // outer catch error: throw err
} catch (error) {
console.error('outer catch error: ', error);
}
複製代碼
async
函數是什麼?簡單來講,它就是Generator
函數的語法糖。Generator
的用法仍是有點晦澀難懂的,用起來總感受有點複雜,因此 ES7 中推出了 async/await
。語法其實也是和 Generator
相似,只是將*
換成async
,yield
換成await
。不過Generator
返回的是一個迭代器,而async/await
返回的則是一個Promise
對象,也就意味着可使用then
、 catch
等方法了。
Generator
函數的執行依賴執行器。而async
函數自帶執行器,將 Generator
函數和自動執行器,包裝在一個函數裏,因此它不須要使用next
方法來逐步控制函數的執行,和普通函數的調用是一致的。
async function getData(data) {
const res1 = await fetch(data);
const res2 = await fetch(res1);
const res3 = await fetch(res2);
const res4 = await fetch(res3);
const res5 = await fetch(res4);
return res5;
}
const finalRes = getData(123);
複製代碼
異步編程的終極解法,就是像同步編程同樣處理異步操做。