本文將從js的異步歷史介紹開始,到親自手寫一個async函數的模擬實現~node
正文開始~git
咱們都知道JavaScript是單線程的,避開了操做多線程的上鎖和複雜的狀態同步問題,單線程是沒法充分利用cpu的,不過早期的JavaScript只是做爲瀏覽器的腳本工具實現的,所以採用單線程是最方便最省事的,做爲瀏覽器腳本語言,JavaScript的主要用途是與用戶互動,以及操做DOM。它只能是單線程,不然會帶來很複雜的同步問題。好比,假定JavaScript同時有兩個線程,一個線程在某個DOM節點上添加內容,另外一個線程刪除了這個節點,這時瀏覽器應該以哪一個線程爲準?es6
受制於單線程的緣由,JavaScript在處理異步任務的時候就出現了比較尷尬的局面,好比AJAX請求,若是事情只能一件一件的來,當用戶到服務器上去請求的時候,必須乾等到結果回來才能繼續後面的動做,這顯而易見是不能被接受的,所以callback順勢而出,到後來的nodejs崛起,實現IO讀取等異步的方式都是採用了callback的方式,也誕生了臭名昭著的callback hell,場面開始失控...github
function (param, cb){
false.readFile(param, function(err, data){
if(err) return cb(err);
async1(data, function(err, data){
if(err) return cb(err);
async2(data, function(err, data){
if(err) return cb(err);
// asyncN... 不知道會有多少
})
})
})
}
複製代碼
個人意中人是蓋世英雄,有一天他會踏着七色雲彩前來拯救我ajax
es6將promise歸入了JavaScript的標準,promise表明着「承諾」,初始化了一個「承諾」以後,你只要在then中定義好這個承諾如果在將來達成要作什麼事情,在catch中定義好這個承諾失敗了須要作什麼事情,就ok了promise
const somePromiseObject = return new Promise((resolve, reject) => {
const result = doSomeThing();
if(result) {
return resolve(result); // handle success
}
reject('err') // handle error
})
somePromiseObject
.then(data => {
// success callback
},
err => {
// err callback
})
複製代碼
promise的出現完美的解救了callback的回調地獄,緣由是promise容許鏈式調用,而且能夠統一到最外層去作錯誤的catch~瀏覽器
somePromiseObject
.then(data => {
// do someThings..
})
.then(data => {
// do someThings..
})
.then(data => {
// do someThings..
})
.catch(err => {
// handle errs here
})
複製代碼
but 當咱們認爲promise就是JavaScript解決異步的最優方案的時候,ES2017 標準又引入了async 函數,使得異步操做變得更加方便bash
我猜中了前頭可我猜不中這結局 --promise服務器
沒錯就在es6將promise加入標準後的不久,es7又新加入一種新的異步解決方案(號稱終極解決方案)-- async函數多線程
假設有個異步函數
ajax1().then(() => {
ajax2().then(() => {
ajax3().then((data) => {
console.log(data) // success
}, err => {
console.log(err)
})
},
err => {
console.log(err)
})
}, err => {
console.log(err)
})
複製代碼
儘管promise能鏈式的處理下去,可是嵌套過多的話,維護起來仍是比較頭疼的,並且錯誤的處理也至關的麻煩,須要每一個可能的錯誤都去關注,因此,咱們趕忙試試用async來實現
const asyncFun = async () => {
try{
await ajax1();
await ajax2();
const data = await ajax3();
console.log(data) // success
}catch(err){
console.log(err) // handle errs here
}
}
複製代碼
能夠看到明顯的看到,async的寫法比promise的鏈式更舒服,它容許咱們像寫同步的語法同樣去寫嵌套的異步,實際的效果也會按照async 被 await 的順序來運行,並且對開發最友好的是全部中間出現的錯誤都能被最外層的catch給捕獲,因此說不少人都認爲async將是js異步的終極解決方案,下面咱們先來認識一下實現async的函數的一個基礎函數,generator函數~
function* gen(x) {
const y = yield x + 2;
const y1 = yield y + 3;
return y1;
}
var g = gen(1);
console.log(g.next()); // { value: 3, done: false }
console.log(g.next(2)); // { value: 5, done: false }
console.log(g.next()); // { value: undefined, done: true }
複製代碼
既然generator能夠暫停,若是咱們每次遇到promise就暫停,等拿到promise.then()執行的結果在返回data,豈不是就是async函數的表現形式?dei,這確實就是async實現的基本原理,下面咱們一塊兒來手擼一個模擬async的函數--"myAsync",盤它!
// 函數A(正常狀況);
const test = async function myGenerator(){
const data = await Promise.resolve("success");
console.log(data);
}
// 函數B(模擬狀況);
function myAsync(myGenerator) {
// handle...
}
function* myGenerator() {
const data = yield Promise.resolve("success");
console.log(data); // success
}
const test = myAsync(myGenerator);
複製代碼
// 函數B(模擬狀況);
function myAsync(myGenerator) {
const gen = myGenerator(); // 生成迭代器
const handle = genResult => {
if (genResult.done) return; // 若是迭代器結束了,直接返回;
return genResult.value instanceof Promise // 判斷當前迭代器的value是不是Promise的實例
? genResult.value
// 若是是,則等待異步完成後繼續遞歸下一個迭代,並把resolve後的data帶過去
.then(data => handle(gen.next(data)))
.catch(err => gen.throw(err)) // gen.throw 能夠拋出一個容許外層去catch的err
: handle(gen.next(genResult.value)); // 若是不是promise,就能夠直接遞歸下一次迭代了
};
try {
handle(gen.next()); // 開始處理next迭代
} catch (err) {
throw err;
}
}
function* myGenerator() {
const data = yield Promise.resolve("success");
console.log(data); // success
}
const test = myAsync(myGenerator);
複製代碼
到如今其實咱們的核心功能handle函數就完成了,如今去調用next()方法,已經看到能夠打印success了,可是,除了正常的promise,咱們還要考慮下面的狀況
// 函數A(正常狀況);
const a = {
then: () => {
console.log("then");
return 123123;
}
};
const test0 = async function() {};
const test1 = async function() {
return 123;
};
const test2 = async function() {
console.log(123);
};
const test4 = async function() {
return a;
};
test0().then(console.log); // undefined
test1().then(console.log); // 123
test2().then(console.log); // 123 undefined
test4().then(console.log); // then
複製代碼
也就是說在async接受到的函數不是generator函數的狀況下:
因此如今的代碼變成
// 函數B(模擬狀況);
function myAsync(myGenerator) {
// 判斷接受到的參數不是一個generator函數
if (
Object.prototype.toString.call(myGenerator) !==
"[object GeneratorFunction]"
) {
// 若是是一個普通函數
if (
Object.prototype.toString.call(myGenerator) === "[object Function]"
) {
return new Promise((resolve, reject) => {
// 默認返回一個promise對象
try {
const data = myGenerator();
return resolve(data); // 嘗試運行這個函數,並把結果resolve出去
} catch (err) {
return reject(err); // 失敗處理
}
});
}
// 若是參數含有then這個方法--thenable 鴨子類型
if (typeof myGenerator.then === "function") {
return new Promise((resolve, reject) => {
try {
// 運行這個對象的then函數,並resolve出去
const data = myGenerator.then();
return resolve(data);
} catch (err) {
return reject(err); // 失敗處理
}
});
}
// 剩下的狀況,統一resolve出去給的參數
return Promise.resolve(myGenerator);
}
const gen = myGenerator(); // 生成迭代器
const handle = genResult => {
if (genResult.done) return; // 若是迭代器結束了,直接返回;
return genResult.value instanceof Promise // 判斷當前迭代器的value是不是Promise的實例
? genResult.value
.then(data => handle(gen.next(data))) // 若是是,則等待異步完成後繼續遞歸下一個迭代,並把resolve後的data帶過去
.catch(err => gen.throw(err)) // gen.throw 能夠拋出一個容許外層去catch的err
: handle(gen.next(genResult.value)); // 若是不是promise,就能夠直接遞歸下一次迭代了
};
try {
handle(gen.next()); // 開始處理next迭代
} catch (err) {
throw err;
}
}
const a = {
then: () => {
console.log("then");
return 123123;
}
};
const test0 = myAsync(function() {});
const test1 = myAsync(function() {
return 123;
});
const test2 = myAsync(function() {
console.log(123);
});
const test4 = myAsync(function() {
return a;
});
test0.then(console.log); // undefined
test1.then(console.log); // 123
test2.then(console.log); // 123 undefined
test4.then(console.log); // then
複製代碼
有的同窗可能會說,咦,es7的 async 函數返回的那個是個函數,須要執行才能拿到結果,好比test0 應該是 test() 返回的是promise,可是模擬出來的怎麼直接就拿到返回的promise了,其實很簡單,你只要再用一個函數(好比myAsyncWrapper)把這個函數包裝一下就ok~
myAsync(function*() {
try {
const data1 = yield new Promise(res => {
setTimeout(() => {
res(1234);
console.log("step 1"); // step 1
}, 1000);
});
console.log(data1); // step 1 打印1s後 打印 123
const data2 = yield new Promise(res => {
setTimeout(() => {
res(12342);
console.log("step 2"); // step 2
}, 1000);
});
console.log(data2); // step 2 打印1s後打印 12342
} catch (err) {
console.log(888, err);
}
});
複製代碼
完美啊有麼有,再來試試reject
myAsync(function*() {
try {
yield Promise.reject(123);
const data1 = yield new Promise(res => {
setTimeout(() => {
res(1234);
console.log("step 1");
}, 1000);
});
console.log(data1);
const data2 = yield new Promise(res => {
setTimeout(() => {
res(12342);
console.log("step 2");
}, 1000);
});
console.log(data2);
} catch (err) {
console.log(888, err); // 888 123
}
});
複製代碼
沒問題直接輸出了888 123