JavaScript中的異步編程

異步

何爲異步?

簡單來說就是一個任務分成多個步驟執行,先執行某一段任務,跳出轉而執行其他任務, 等下一段任務準備完成後, 轉而回來執行下一段任務

像這種類型, 把一個任務分解成多段任務 不連續 執行, 就叫做異步, 連續執行的則叫做同步

如何使得異步 看起來像是同步編程 ? 有如下幾種方法

  • 回調函數

通過拆解一個任務, 分成多段,把第二段任務單獨寫在第二個函數內,等到需要執行這個任務時, 直接調用這個函數

node.js中常用的就是如此方法。

    fs.readFile('某個文件', function (err, data) {
        if (err) throw err;
        console.log(data);
    });

這是一個錯誤優先的回調函數(error-first callbacks),這也是Node.js本身的特點之一。 類似golang中的err 錯誤處理
##### 帶來的問題:

  • 多層嵌套問題

回調帶來一些問題, 第一個就是多層嵌套問題, 當一個問題很複雜, 多段不連續, 就會出現地獄嵌套問題

fs.readFile('某個文件', function (err, data) {
    if (err) throw err;
    fs.writeFile('某個文件',data, function (err, data) {
        if (err) throw err;
            fs.readFile('某個文件', function (err, data) {
                if (err) throw err;
                console.log("寫入的是:",data)
        });
    });
});
  • 異常處理

無法使用try{}catch(){} 捕獲錯誤
列子:

    try{
        setTimeout(()=>{
            callback()
            throw new Error('拋出錯誤')
        },1000)
    }.catch(err){
        console.log('看看是否走到了這裏')
    }

上面的代碼是無法走到catch內部的, 由於try{}catch 只能捕獲當前任務循環內的任務拋出錯誤, 而這個回調被存放起來, 直到下一個事件環的時候纔會取出, try{}catch實在無能爲力

在node中,已約定回調的第一個參數是拋出的異常。只是用另外的方式來捕獲錯誤。
僞代碼

    let func = function(callback){
        try{
                setTimeout(()=>{
                if(success){
                    callback(null)
                }else{
                    callback(new Error('錯誤'))
                }
            },1000)
        }catch(e){
            console.log('捕獲錯誤',e);
        }
        
    }
  • 事件監聽

通常在前端操作的一般是通過addeventLisener監聽各種事件,比如鍵盤事件 鼠標事件等等,

    document.addeventListener('click',function(e){
        console.log(e.target)
    },false)
  • 事件發佈訂閱

通常把需要執行的任務先暫存起來, 等達到條件或者發佈的時候一一拿出來執行

  • 僞代碼
class Task{
    construct(){
        this.tasks = {}
    }
    publish(event){
        this.tasks[event].forEach(fn=>fn())
    }
    subscribe(event,eventTask){
        this.tasks[event] =   this.tasks[event] ?  this.tasks[event] : []
        this.tasks[event].push(eventTask)
    }
}
let task = new Task()
task.subscribe('eat',function(){console.log('吃午飯')})
task.subscribe('eat',function(){console.log('吃晚飯')})
task.publish('eat')
  • Promise/Deferred模式

    • 生成器Generators/ yield

      • 當你在執行一個函數的時候,你可以在某個點暫停函數的執行,並且做一些其他工作,然後再返回這個函數繼續執行, 甚至是攜帶一些新的值,然後繼續執行。
      • 上面描述的場景正是JavaScript生成器函數所致力於解決的問題。當我們調用一個生成器函數的時候,它並不會立即執行, 而是需要我們手動的去執行迭代操作(next方法)。也就是說,你調用生成器函數,它會返回給你一個迭代器。迭代器會遍歷每個中斷點。
      • next 方法返回值的 value 屬性,是 Generator 函數向外輸出數據;next 方法還可以接受參數,這是向 Generator 函數體內輸入數據
    function* foo () {
        var index = 0;
        while (index < 2) {
            yield index++; //暫停函數執行,並執行yield後的操作
        }
    }
    var bar =  foo(); // 返回的其實是一個迭代器

    console.log(bar.next());    // { value: 0, done: false }
    console.log(bar.next());    // { value: 1, done: false }
    console.log(bar.next());    // { value: undefined, done: true }

yield具體查看mdn文檔

yield是一個表達式, 後面緊跟着的表達式是next()的返回結果的value,而如果想給yield傳遞參數,比如a=yield 1,給a傳遞值 則要next(value))

// 例子: 
 function* foo () {
        a = yield 1
        console.log(a) // 10
    }
    var bar =  foo(); // 返回的其實是一個迭代器
    console.log(bar.next());  // { value: 1, done: false }
    console.log(bar.next(10));   // { value: undefined, done: true }

可以理解爲yield有兩步操作,第一個next彈出值,第二個next接收值並且執行一下段語句,直到下一個yield彈出值爲止

利用yield轉換多維數組
function* iterArr(arr) {            //迭代器返回一個迭代器對象
  if (Array.isArray(arr)) {         // 內節點
      for(let i=0; i < arr.length; i++) {
          yield* iterArr(arr[i]);   // (*)遞歸
      }
  } else {                          // 離開     
      yield arr;
  }
}

var arr = [ 'a', ['b',[ 'c', ['d', 'e']]]];
var gen = iterArr(arr);
arr = [...gen];   
利用yield解決異步問題
function* main(){
    try{
        let result = yield foo()
        console.log(result)
    }catch(e){ 
        console.log(e)
    }
  
}
let it = main()

function foo(params,url){
    $.ajax('www.baidu.com',
    function(err,data){
        if(err){
            it.throw(err)
        }else{
            it.next(data)
        }
    }
}

it.next()  // {value:undefind,done:false}
  • promise

Promise 對象用於表示一個異步操作的最終完成 (或失敗), 及其結果值.
他有三個狀態 分別是pending resolved 以及rejected
一旦發生狀態改變, 就不可再更改了 每次then都另外創建一個promise對象

    //  僞代碼
    class Promise{
        construct(executor){
            let self = this
            // 一個 Promise有以下幾種狀態:
            // pending: 初始狀態,既不是成功,也不是失敗狀態。
            // fulfilled: 意味着操作成功完成。
            // rejected: 意味着操作失敗。
            this.status = 'pending' 
            this.res = undefined   // 存成功之後的值
            this.err = undefined   // 存失敗之後的值
            this.onFulfilledCallback = []
            this.onRejectedCallback = []
            function resolve(res){
                if(self.status === 'pending'){
                    self.status = 'resolved'
                    self.res = res 
                    onFulfilledCallback.forEach(fn=>fn())
                }
            }
            function reject(err){
                if(self.status === 'pending'){
                    self.status = 'rejected'
                    self.err = err 
                    onRejectedCallback.forEach(fn=>fn())
                }
            }
            // executor是帶有 resolve 和 reject 兩個參數的函數 。Promise構造函數執行時立即調用executor 函數, resolve 和 reject 兩個函數作爲參數傳遞給executor(executor 函數在Promise構造函數返回所建promise實例對象前被調用)。resolve 和 reject 函數被調用時,分別將promise的狀態改爲fulfilled(完成)或rejected(失敗)。executor 內部通常會執行一些異步操作,一旦異步操作執行完畢(可能成功/失敗),要麼調用resolve函數來將promise狀態改成fulfilled,要麼調用reject 函數將promise的狀態改爲rejected。如果在executor函數中拋出一個錯誤,那麼該promise 狀態爲rejected。executor函數的返回值被忽略。
            executor(resolve,reject)
        }
        then(onFulfilled,onRejected){
            let self = this
            return new Promise((resolve,reject)=>{
                if(self.status === 'resolved'){
                    let x = onFulfilled(self.res)   // 拿到onFulfilled的執行結果  注意:這裏執行的是Promise.resolve() 同步代碼
                    // 然後把x傳遞給下一個then
                    resolve(x)
                }
                 if(self.status === 'rejected'){
                    let x = onRejected(self.res)   // 拿到onFulfilled的執行結果  注意:這裏執行的是Promise.resolve() 同步代碼
                    // 然後把x傳遞給下一個then
                    reject(x)
                }
                if(self.status === 'pending'){
                    self.onFulfilledCallback.push(function(){
                        let x = onFulfilled(self.res)  // 這裏的self.res 是上一個new Promise上的值 此時的onFUlfilled 相當於 fn(){let x = onFulfilled}  
                        resolve(x)
                    })
                    self.onRejectedCallback.push(function(){
                        let x = onRejected(self.res)  // 這裏的self.res 是上一個new Promise上的值 此時的onFUlfilled 相當於 fn(){let x = onFulfilled}  
                        reject(x)
                    })
                }
            })
        }
    }
使用promise
function request(){
    return new Promise((resolve,reject)=>{
        setTimeout(()=>{
            resolve({data:'獲得數據'})
        },1000)
    })
}
request().then(data=>{
    console.log(data)
})

鏈接:Promise

yield 配合Promise
function foo(x,y) {
    return request(
        "http://some.url.1/?x=" + x + "&y=" + y
    );
} 
function *main() {
    try {
        var text = yield foo( 11, 31 );
        console.log( text );
    }
    catch (err) {
        console.error( err );
    }
} 
let it = main()
it.next()

var text = yield foo( 11, 31 )跟async await 是不是很像?

編寫一個生成器

生成器可以 yield 一個 promise,然後這個 promise 可以被綁定,用其完成值來恢復這個生成器的運行。

//  僞代碼
function run(gen){  // 參數是一個gen函數
    let it = gen.apply(this)
    return Promise.resolve().then((value)=>{
        let next = it.next(value)
        
        return (function nextHandle(next){
            if(next.done === true){
                return next.value
            }else{
                return Promise.resolve(next.value).then(nextHandle)  // 遞歸
            }
        })(next)
    })

}

function* main(){
    function *main() {
        try {
            var text = yield foo( 11, 31 );
            console.log( text );
        }
        catch (err) {
            console.error( err );
        }
    } 
}

run(main).then((data)=>{
    // do something
}) 

AsyncFunction

AsyncFunction 構造函數用來創建新的 異步函數 對象,JavaScript 中每個異步函數都是 AsyncFunction 的對象。

注意,AsyncFunction 並不是一個全局對象,需要通過下面的方法來獲取

Object.getPrototypeOf(async function(){}).constructor

語法:new AsyncFunction([arg1[, arg2[, ...argN]],] functionBody)

var AsyncFunction = Object.getPrototypeOf(async function(){}).constructor;
var a = new AsyncFunction('a', 
                          'b',
                          'return await resolveAfter2Seconds(a) + await resolveAfter2Seconds(b);');
a(10, 20).then(v => {
  console.log(v); // 4 秒後打印 30
});

但是上面這種方式不高效 因爲通過字面量創建的異步函數是與其他代碼一起被解釋器解析的,而new這種方式的函數體是單獨解析的。

通過字面量創建
    async a(a,b){
        return await resolveAfter2Seconds(a) + await resolveAfter2Seconds(b)
    }

await 表達式會暫停當前 async function 的執行,等待 Promise 處理完成。若 Promise 正常處理(fulfilled),其回調的resolve函數參數作爲 await 表達式的值,繼續執行 async function。

若 Promise 處理異常(rejected),await 表達式會把 Promise 的異常原因拋出。

另外,如果 await 操作符後的表達式的值不是一個 Promise,則返回該值本身。

function resolveAfter2Seconds(x) {
  return new Promise(resolve => {
    setTimeout(() => {
      resolve(x);
    }, 2000);
  });
}

async function f1() {
  var x = await resolveAfter2Seconds(10);
  console.log(x); // 10
}
f1();
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章