1. 程式人生 > 實用技巧 >Javascript非同步程式設計

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)這個訊號,從而知道什麼時候自己可以開始執行。這就叫做

"釋出/訂閱模式"(publish-subscribe pattern),又稱"觀察者模式"(observer pattern)。這個模式有多種實現,下面採用的是Ben Alman的Tiny Pub/Sub,這是jQuery的一個外掛。

首先,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 那一行程式碼才算執行完,才繼續往下一行執行。