拿下異步
寫在前面
今天來回顧一下異步,在如今的前端。異步已經是非常重要的東西了,甚至可以說是不懂異步,什麼都做不了了
今天從幾個方面來總結
- 總結一下常見的異步解決方案
- 手寫一下promise
異步經歷了以下四個階段
回調函數 —> Promise —> Generator —> async/await。
回顧一下開始容易感到困惑的問題
問:首先我們都清楚js是一個單線程的語言,單又可以開一個異步任務的線程是否與它的單線程執行模式自相矛盾呢?
答:js的執行時單線程的,但是瀏覽器是多線程
的。即js的異步是由js執行線程和瀏覽器的事件觸發線程等共同實現的
(結合Electron開發的經驗理解瀏覽中的多線程操作)。進程之間不太容易實現交流,線程可是沒有這個憂慮的。js執行線程即遇到一個異步代碼,會向瀏覽器請求援助。瀏覽器會新開一個線程處理,事件監聽線程會監聽處理的結果
步入正題(常見的異步解決方案)
1. 回調函數
直接看栗子(讀取一個文本):
const fs = require("fs");
const path = require("path")
fs.readFile(path.join(__dirname, "./test.txt"), "utf8", (err, data) => {
console.log(data);
})
2. 發佈訂閱
寫一個“信號中心”。當一個異步任務執行結束之後向內發佈“信號”,則訂閱者的回調就可開始執行(發佈訂閱模式)
class Event {
constructor() {
this.subs = [];
}
on(fn) {
this.subs.push(fn)
}
emit(data) {
this.subs.forEach(fn => fn(data));
}
}
const fs = require("fs");
const path = require("path")
let e = new Event();
e.on((val) => {
console.log(val);
})
fs.readFile(path.join(__dirname, "./test.txt"), "utf8", (err, data) => {
if (err) return;
e.emit(data);
})
注意:有一些文檔說發佈訂閱模式就是觀察者模式,這是不對的。發佈訂閱更像是觀察者的子集(日後總結設計模式的時候再展開吧)
3. 事件監聽
這個東西,寫個前端的都寫過了
demo.onclick=function(){}
4. promise
爲了解決回調地獄的問題,promise出來了
const fs = require("fs");
const path = require("path")
const p = new Promise((resolve, reject) => {
fs.readFile(path.join(__dirname, "./test.txt"), "utf8", (err, data) => {
if (err) {
reject(err);
} else {
resolve(data)
}
})
})
p.then(data => {
console.log(data);
})
注意promise的使用和原理是一個重點,筆者在下面會總結
5. generator+co
利用generator
可以中斷可以喚醒的執行機制。
爲什麼需要co
?我們都知道generator
是一個迭代器生成器,它調用一下才是一個迭代器,同時因爲要喚醒我們也要不斷則執行next()
方法。這便很繁瑣了,co
就是爲我們封裝了這麼一個自動執行的東西。
這裏爲了栗子的簡便,筆者沒有使用co
。主要要了解它做了什麼
const fs = require("fs");
const path = require("path");
const ioPromise = new Promise((resolve, reject) => {
fs.readFile(path.join(__dirname, "./test.txt"), "utf8", (err, data) => {
if (err) {
reject(err);
} else {
resolve(data)
}
})
});
function* read() {
const data = yield ioPromise;
}
const it = read();
it.next().value.then(data => {
console.log(data);
});
6. async/awiat(generator+co的語法糖)
這個就是我們現在常用的啦
async function getData() {
const res = await this.$aixos("...")
}
第二階段 promise的相關知識
1. 常用方法
原型上
-
then():接收兩個回調參數,後一個回調是可選參數,
then(function(){...},function(){....})
它的返回值需要注意一下(直接看mdn,這裏很值得注意,因爲手寫時這裏的邏輯很重要)
- 返回了一個值,那麼
then
返回的 Promise 將會成爲接受狀態,並且將返回的值作爲接受狀態的回調函數的參數值。 - 沒有返回任何值,那麼
then
返回的 Promise 將會成爲接受狀態,並且該接受狀態的回調函數的參數值爲undefined
。 - 拋出一個錯誤,那麼
then
返回的 Promise 將會成爲拒絕狀態,並且將拋出的錯誤作爲拒絕狀態的回調函數的參數值。 - 返回一個已經是接受狀態的 Promise,那麼
then
返回的 Promise 也會成爲接受狀態,並且將那個 Promise 的接受狀態的回調函數的參數值作爲該被返回的Promise的接受狀態回調函數的參數值。 - 返回一個已經是拒絕狀態的 Promise,那麼
then
返回的 Promise 也會成爲拒絕狀態,並且將那個 Promise 的拒絕狀態的回調函數的參數值作爲該被返回的Promise的拒絕狀態回調函數的參數值。 - 返回一個未定狀態(
pending
)的 Promise,那麼then
返回 Promise 的狀態也是未定的,並且它的終態與那個 Promise 的終態相同;同時,它變爲終態時調用的回調函數參數與那個 Promise 變爲終態時的回調函數的參數是相同的。
- 返回了一個值,那麼
-
catch():
then(null,function(){})
的別名。promise狀態失敗了走它。這裏要注意的是在resolve之後的錯誤是不會捕獲的,同時promise的錯誤具有“冒泡的性質”,它會一直向後傳遞直到被捕獲。故建議將catch寫到最後- 返回值:一個promise
-
finally():不管promise的狀態是成功還失敗都走一下這個方法
因爲then
的返回值也均是promise,故爲promise的鏈式調用提供了可能
靜態方法
-
resolve: 返回一個狀態由給定value決定的Promise對象。如果該值是thenable(即,帶有then方法的對象),返回的Promise對象的最終狀態由then方法執行決定;否則的話(該value爲空,基本類型或者不帶then方法的對象),返回的Promise對象狀態爲fulfilled,並且將該value傳遞給對應的then方法。通常而言,如果你不知道一個值是否是Promise對象,使用Promise.resolve(value) 來返回一個Promise對象,這樣就能將該value以Promise對象形式使用。
-
reject: 返回一個狀態爲失敗的Promise對象,並將給定的失敗信息傳遞給對應的處理方法
-
all:參數(iterable)一般是元素是promise的數組,即[p1,p2,p3]。幾個promise全resolve,all的返回值也是一個已完成的promise。有一個reject,則走失敗
-
race:與all相似,[p1,p2,p3]哪一個promise最先狀態改變它就認哪個。
2. 手寫promise
**Promise**
對象是一個代理對象(代理一個值),被代理的值在Promise對象創建時可能是未知的。它允許你爲異步操作的成功和失敗分別綁定相應的處理方法(handlers)。 這讓異步方法可以像同步方法那樣返回值,但並不是立即返回最終執行結果,而是一個能代表未來出現的結果的promise對象
一個 Promise
有以下幾種狀態:
- pending: 初始狀態,既不是成功,也不是失敗狀態。
- fulfilled: 意味着操作成功完成。
- rejected: 意味着操作失敗。
手寫之前,回想一下promise是怎麼使用的。我們new的時候穿了一個執行函數,且當裏面異步邏輯成功時執行resolve,失敗時執行reject
即開始一個promise的狀態是 pending,執行了resolve它就變成了fulfilled,又或者執行了reject它就變成了rejected
並且promise的狀態是發生了改變便不會再變動了,成功就是成功。不會從成功態又轉到失敗態
1 來構造這個類(這個已經是最簡版了)
const PENDING = "PENDING"; //運行態
const SUCCESS = "SUCCESS"; //成功態
const FUL = "FUL"; //失敗態
class MyPromise {
constructor(exector) {
this.status = PENDING;
this.res = undefined; //失敗原因
this.val = undefined; //成功值
let resolve = (val) => {
if (this.status != PENDING) return;
this.val = val;
this.status = SUCCESS;
}
let reject = (err) => {
if (this.status != PENDING) return;
this.res = err;
this.status = FUL;
}
try {
exector(resolve, reject);
} catch (error) {
reject(error);
}
}
then(onfulfilled, omrejected) {
if (this.status === SUCCESSS) {
onfulfilled(this.val)
}
if (this.status === FUl) {
omrejected(this.res);
}
}
}
2. 增加異步解決
上面的實現,若是一個異步任務。走到then時的狀態還是PENDING
走不通。
利用發佈訂閱模式,還是在異步結果出來之發佈信號。then中實現訂閱即可
class MyPromise {
constructor(exector) {
this.status = PENDING;
this.res = undefined;
this.val = undefined;
//裝then成功的回調
this.onfulfilledCb = [];
//裝then失敗的回調
this.onrejectedCb = [];
let resolve = (val) => {
if (this.status != PENDING) return;
this.val = val;
this.status = SUCCESS;
//成功結果出來執行已訂閱的的回調
this.onfulfilledCb.forEach(fn => fn(val))
}
let reject = (err) => {
if (this.status != PENDING) return;
this.res = err;
this.status = FUL;
//失敗結果出來執行已訂閱的的回調
this.onrejectedCb.forEach(fn => fn(err));
}
try {
exector(resolve, reject);
} catch (error) {
reject(error);
}
}
then(onfulfilled, onrejected) {
if (this.status === SUCCESS) {
onfulfilled(this.val);
}
if (this.status === FUL) {
onrejected(this.res);
}
// 異步
if (this.status === PENDING) {
this.onfulfilledCb.push(onfulfilled);
this.onrejectedCb.push(onrejected);
}
}
}
3. 爲了保證鏈式調用then必須是返回一個promise
注意上面常用方法中then的返回值類型,用setTimeout包一下是因爲promise的規範是明確說明:then方法是異步執行的,故這裏使用setTimeout模擬了一下
const resolvePromise = (promise2, x, resolve, reject) => {
if (typeof x === "object" || typeof x === "function") {
let then = x.then;
// 有then方法默認它就是promise
if (typeof then === "function") {
then.call(x, y => {
// 可能返回的還是一個promise,故遞歸處理
resolvePromise(promise2, y, resolve, reject)
}, z => {
reject(z);
})
}
} else {
// 簡單類型的值直接resolve出去
resolve(x);
}
}
const PENDING = "PENDING";
const SUCCESS = "SUCCESS";
const FUL = "FUL";
class MyPromise {
constructor(exector) {
this.status = PENDING;
this.res = undefined;
this.val = undefined;
this.onfulfilledCb = [];
this.onrejectedCb = [];
let resolve = (val) => {
if (this.status != PENDING) return;
this.val = val;
this.status = SUCCESS;
this.onfulfilledCb.forEach(fn => fn())
}
let reject = (err) => {
if (this.status != PENDING) return;
this.res = err;
this.status = FUL;
this.onrejectedCb.forEach(fn => fn());
}
try {
exector(resolve, reject);
} catch (error) {
reject(error);
}
}
then(onfulfilled, onrejected) {
let promise2 = new MyPromise((resolve, reject) => {
if (this.status === SUCCESS) {
setTimeout(() => {
let x = onfulfilled(this.val);
resolvePromise(promise2, x, resolve, reject)
}, 0)
}
if (this.status === FUL) {
setTimeout(() => {
let x = onrejected(this.res);
resolvePromise(promise2, x, resolve, reject)
}, 0)
}
// 異步
if (this.status === PENDING) {
this.onfulfilledCb.push(() => {
setTimeout(() => {
let x = onfulfilled(this.val);
resolvePromise(promise2, x, resolve, reject)
}, 0)
});
this.onrejectedCb.push(() => {
setTimeout(() => {
let x = onrejected(this.res);
resolvePromise(promise2, x, resolve, reject)
}, 0)
});
}
})
return promise2;
}
}
4 全部代碼
const resolvePromise = (promise2, x, resolve, reject) => {
if (typeof x === "object" || typeof x === "function") {
let then = x.then;
// 有then方法默認它就是promise
if (typeof then === "function") {
then.call(x, y => {
// 可能返回的還是一個promise,故遞歸處理
resolvePromise(promise2, y, resolve, reject)
}, z => {
reject(z);
})
}
} else {
// 簡單類型的值直接resolve出去
resolve(x);
}
}
const PENDING = "PENDING";
const SUCCESS = "SUCCESS";
const FUL = "FUL";
class MyPromise {
constructor(exector) {
this.status = PENDING;
this.res = undefined;
this.val = undefined;
this.onfulfilledCb = [];
this.onrejectedCb = [];
let resolve = (val) => {
if (this.status != PENDING) return;
this.val = val;
this.status = SUCCESS;
this.onfulfilledCb.forEach(fn => fn())
}
let reject = (err) => {
if (this.status != PENDING) return;
this.res = err;
this.status = FUL;
this.onrejectedCb.forEach(fn => fn());
}
try {
exector(resolve, reject);
} catch (error) {
reject(error);
}
}
then(onfulfilled, onrejected) {
let promise2 = new MyPromise((resolve, reject) => {
if (this.status === SUCCESS) {
setTimeout(() => {
let x = onfulfilled(this.val);
resolvePromise(promise2, x, resolve, reject)
}, 0)
}
if (this.status === FUL) {
setTimeout(() => {
let x = onrejected(this.res);
resolvePromise(promise2, x, resolve, reject)
}, 0)
}
// 異步
if (this.status === PENDING) {
this.onfulfilledCb.push(() => {
setTimeout(() => {
let x = onfulfilled(this.val);
resolvePromise(promise2, x, resolve, reject)
}, 0)
});
this.onrejectedCb.push(() => {
setTimeout(() => {
let x = onrejected(this.res);
resolvePromise(promise2, x, resolve, reject)
}, 0)
});
}
})
return promise2;
}
}
let p = new MyPromise((resolve, reject) => {
setTimeout(() => {
resolve(1000)
}, 1000)
});
p.then(data => {
console.log(data);
return new MyPromise((resolve, reject) => {
setTimeout(() => {
resolve(1000)
}, 1000)
})
}).then(data => {
console.log(data);
})