Javascript非同步程式設計
1:出現非同步的原因
JavaScript執行環境是單執行緒的,所以同一個時間只能執行一次任務,後面任務佇列都等待著前一個任務執行完畢才能夠執行,為了解決這個問題,Javascript將任務分為同步和非同步兩種執行模式
2:回撥函式處理非同步
這是最基本的非同步處理方式了
//加入兩個函式
f1() ---耗時很長 會阻塞後面程式碼
f2()
//改寫f1
funtion f1(callback){
setTimeout(function(){
//f1程式碼
callback()
})
}//執行程式碼就程式設計這個樣子 f1(f2);
採用這種方式,就可以把同步程式設計非同步操作了,f1不會阻塞任何程式,相當於執行了程式的主要邏輯,但是程式碼高度耦合,流程會很亂,而且每個人物只能指定一個回撥函式
3:事件監聽處理
這種方式採用的是事件驅動,這種方式不會考慮程式碼編寫順序,而是取決於某個時間是否觸發。
f1.on('done',f2);//註冊監聽
funtioin f1(){
setTimeout(function(){
f1.trigger("done") //觸發done,執行f2
})
}
這種方法有點很明顯,可以繫結多個事件,每個事件指定多個回撥,而且可以去耦合,有利於實現模組化,缺點是過度依賴驅動,事件流程變得不清晰。
4:釋出和訂閱
上一節的"事件",完全可以理解成"訊號"。
我們假定,存在一個"訊號中心",某個任務執行完成,就向訊號中心"釋出"(publish)一個訊號,其他任務可以向訊號中心"訂閱"(subscribe)這個訊號,從而知道什麼時候自己可以開始執行。這就叫做
首先,f2向"訊號中心"jQuery訂閱"done"訊號。
jQuery.subscribe("done", f2);
然後,f1進行如下改寫:
jQuery.subscribe("done", f2); function f1(){ setTimeout(function () { // f1的任務程式碼 jQuery.publish("done"); }, 1000); }
jQuery.publish("done")的意思是,f1執行完成後,向"訊號中心"jQuery釋出"done"訊號,從而引發f2的執行。
此外,f2完成執行後,也可以取消訂閱(unsubscribe)。
jQuery.unsubscribe("done", f2);
這種方法的性質與"事件監聽"類似,但是明顯優於後者。因為我們可以通過檢視"訊息中心",瞭解存在多少訊號、每個訊號有多少訂閱者,從而監控程式的執行。
5:Promiss物件
這個例項化之後無論成功或者失敗,都會返回一個函式供接下來的(then
error
)等操作使用
new Promiss((resole,reject) => {
//f1程式碼
//成功回撥 resolveI(引數) 或者 失敗回撥 reject(回撥)
}).then( () => {
//接下來操作
})
6:next方法 + promiss
理解next()使用
總結流程:
(1)執行next後會從上往下依次返回每個yield表示式的值,
(2)如果next有傳參的話,會整個覆蓋掉將要返回當前yield的上一個yield,
(3)方法內的yield表示式和return都執行完了,就會直接返回undefined了。
入參 分析結果
1 1(無論是否傳參,傳什麼引數,返回第一個表示式的值1)
3 3(返回第二個表示式的值yield a,a=上一個yield表示式yield 1,被引數3覆蓋,所以a=3)
4 4(返回第三個表示式的值yield b,b=上一個yield表示式yield a,被引數4覆蓋,所以b=4)
5 12(返回return a+b+c,c=上一個yield表示式yield b,被引數5覆蓋,所以c=5,也就是a+b+c=3+4+5=12)
這下沒錯了,太不容易了
解決非同步
const fs = require('fs')
// Promise 版的readFile
const readFile = function (fileName) {
return new Promise(function(resolve, reject) {
fs.readFile(fileName, function(err, data){
if (err) return reject(error);
resolve(data);
})
})
}
const gen = function * () {
let f1 = yield readFile('a.txt');
let f2 = yield readFile('b.txt');
console.log('F1--->', f1.toString());
console.log('F2--->', f2.toString());
}
// 基於 Generator 和 Promise 的自動執行器
function run(gen) {
let g = gen();
function next(data) {
let result = g.next(data);
if (result.done) return result.value;
result.value.then(function(data) {
next(data);
});
}
next();
}
run(gen);
7:基於async和await的非同步方式
await 所在的那一行語句是同步的,將會等待當前這一步的執行結果
const fs = require('fs')
// Promise 版的readFile
const readFile = function (fileName) {
return new Promise(function(resolve, reject) {
fs.readFile(fileName, function(err, data){
if (err) return reject(err);
resolve(data);
})
})
}
const asyncReadFile = async function () {
const f1 = await readFile('a.txt');
const f2 = await readFile('b.txt');
console.log(f1.toString());
console.log(f2.toString());
};
asyncReadFile();
整體是一個非同步函式 不難理解。在實現上,我們不妨逆向一下,語言層面讓async關鍵字呼叫時,在函式執行的末尾強制增加一個promise 反回:內部是同步的 是怎麼做到的?實際上 await 呼叫,是讓後邊的語句(函式)做了一個遞迴執行,直到獲取到結果並使其 狀態 變更,才會 resolve 掉,而只有 resolve 掉,await 那一行程式碼才算執行完,才繼續往下一行執行。