從js來聊聊異步編程

文章的目的

揭開go的 gorouter,c#的 async/await等 使用同步的寫法寫異步代碼的神祕面紗 , 證明其本質就是一個語法糖

爲什麼使用js來講異步編程

因爲js可以通過編程語言自己的語法特性,實現async/await語法

js異步最底層寫法promise

const promise = new Promise(function(resolve, reject) {
  xxxxx.異步IO操作((res)=>{
      if(res成功){
          resolve(res)
      }else{
          reject(res)
      }
  })
});

promise出入的回調函數有一定的要求

  • resolve函數的作用是,將Promise對象的狀態從“未完成”變爲“成功”(即從 pending 變爲 resolved),在異步操作成功時調用,並將異步操作的結果,作爲參數傳遞出去
  • reject函數的作用是,將Promise對象的狀態從“未完成”變爲“失敗”(即從 pending 變爲 rejected),在異步操作失敗時調用,並將異步操作報出的錯誤,作爲參數傳遞出去。
Promise實例生成以後,可以用then方法分別指定resolved狀態和rejected狀態的回調函數(處理返回的結果)。
promise.then(function(value) {
  // success
}, function(error) {
  // failure
});
引申-注意: promise對象在js中非常特殊,比如下面的例子
const p1 = new Promise(function (resolve, reject) {
  setTimeout(() => reject(new Error('fail')), 3000)
})

const p2 = new Promise(function (resolve, reject) {
  setTimeout(() => resolve(p1), 1000)
})
p2
  .then(result => console.log(result))
  .catch(error => console.log(error))
這個的結果是failt 因爲 p2中resolve返回一個promise對象,這個操作將會導致p2的狀態升級成p1的狀態(標準)

promise的then鏈式寫法

promise then方法將會返回一個promise,所以js支持鏈式異步

var getJSON = function (url, callback) {
    var promise = new Promise(function (resolve, reject) {
        var client = new XMLHttpRequest();
        client.open("GET", url);
        client.onreadystatechange = handler;//readyState屬性的值由一個值變爲另一個值時,都會觸發readystatechange事件
        client.responseType = "json";
        client.setRequestHeader("Accept", "application/json");
        client.send();

        function handler() {
            if (this.readyState !== 4) {
                return;
            }
            if (this.status === 200) {
                callback(this.response);
                resolve(this.response);
            } else {
                reject(new Error(this.statusText))
            }
        };
    });
    return promise;
};
getJSON("./e2e-tests/get.json", function (resp) {
    console.log("get:" + resp.name);
}).then(function (json) {
    getJSON("./e2e-tests/get2.json", function (resp) {
        console.log("get2:" + resp.name);
    })
}).catch(function (error) {
    console.log("error1:" + error);
});

promise 異常捕獲

p.then((val) => console.log('fulfilled:', val))
  .catch((err) => console.log('rejected', err));

// 等同於
p.then((val) => console.log('fulfilled:', val))
  .then(null, (err) => console.log("rejected:", err));

這個異常捕獲和java相同,捕獲在eventLoop中產生的異常

注意一點這個異常和java的try catch是不同的,如果產生了異常將不會在主線程中顯示出來

promise的finally

這個和java的異常體系相同,finally 無關狀態,最後都會執行

Promise.resolve(2).finally(() => {})

更加方便的編寫異步使用Promise.resolve(xxx)

Promise.resolve('foo')
// 等價於
new Promise(resolve => resolve('foo'))
注意: promise異步化結果只能在回調函數中獲得,如果異步的操作太多,將會調至調用鏈路過長

如何解決js的promise異步編程的問題?

promise 寫法有什麼問題? ---- 調用鏈路過長

比如: 使用promise 實現 異步ajax請求

var getJSON = function (url, callback) {
    var promise = new Promise(function (resolve, reject) {
        var client = new XMLHttpRequest();
        client.open("GET", url);
        client.onreadystatechange = handler;//readyState屬性的值由一個值變爲另一個值時,都會觸發readystatechange事件
        client.responseType = "json";
        client.setRequestHeader("Accept", "application/json");
        client.send();
        function handler() {
            if (this.readyState !== 4) {
                return;
            }
            if (this.status === 200) {
                callback(this.response);
                resolve(this.response);
            } else {
                reject(new Error(this.statusText))
            }
        };
    });
    return promise;
};
getJSON("./e2e-tests/get.json", function (resp) {
    console.log("get:" + resp.name);
}).then(function (json) {
    getJSON("./e2e-tests/get2.json", function (resp) {
        console.log("get2:" + resp.name);
    })
}).catch(function (error) {
    console.log("error1:" + error);
});

調用鏈太長,不停的promise調用

js如何解決回調地獄---同步方法寫異步

解決方法 使用js的協程 --Generator

generator:js的特殊語法,使用yield 關鍵字將函數分塊了,然後可以使用遍歷器手動控制執行

例子:

function * gen(){
    let a= 123;
    let b = yield a;
    let c = yield a+b;
    return a+b+c;
}

let start = gen();

console.log(start.next());
console.log(start.next(2));
console.log(start.next(3));
本質上是函數分片

js在每次yield的時候都會獲得當前位置的表達式,然後再手動的嵌入就可以實現分片控制的效果了

怎麼用generator實現異步化呢 -- yield配合promise實現異步

看一下這個方法

function* asyncFn(value) {
    let a = yield promiseOne(value);
    let b = yield promiseTwo(a);
    return a + b;
}

想讓他能異步執行,只要能讓前一個promise的結果是下一個promise的輸入就可以了

這裏有兩種寫法

寫法一

遞歸方程: f(最終結果) = f(到目前的結果)+f(接下來執行的結果)

function promiseOne(xxx) {
    return new Promise((res, rej) => {
        res(xxx + 1);
    })
}
function promiseTwo(xxx) {
    return new Promise((res, rej) => {
        res(xxx + 1);
    })
}
function* asyncFn(value) {
    let a = yield promiseOne(value);
    let b = yield promiseTwo(a);
    return a + b;
}
function runAsync(fn,value) {
    let item = fn.next(value);
    return new Promise((res, rej) => {
        if (!item.done) {
            if (item.value instanceof Promise) {
                item.value.then((re)=>{
                    runAsync(fn,re).then(res);
                })
            } else {
                runAsync(fn,fn.valueOf()).then(res);
            }
        } else {
            res(item.value);//這個res方法其實是所有人的res方法
        }
    })
}
runAsync(asyncFn(12)).then(res=>{
    console.log(res);
});
co 工具包的寫法
function run (gen) {
  gen = gen()
  return next(gen.next())
  function next ({done, value}) {
    return new Promise(resolve => {
     if (done) { // finish
       resolve(value)
     } else { // not yet
       value.then(data => {
         next(gen.next(data)).then(resolve)
       })
     }
   })
  }
}
function getRandom () {
  return new Promise(resolve => {
    setTimeout(_ => resolve(Math.random() * 10 | 0), 1000)
  })
}
function * main () {
  let num1 = yield getRandom()
  let num2 = yield getRandom()
 
  return num1 + num2
}
run(main).then(data => {
  console.log(`got data: ${data}`);
})

寫法二

遞歸方程 f(最終結果) = f(之前所有的結果)+f(最後一步的結果)

//同步方式寫異步
function asyncRun(resf, fn, value) {
    let a = fn(value);
    go(value);
    function go(value) {
        let next = a.next(value);
        if (!next.done) {
            if (next.value instanceof Promise) {
                next.value.then((res) => {
                    go(res);
                });
            } else {
                return go(next.value);
            }
        } else {
            resf(next.value);
        }
    }
}
function* asyncFn(value) {
    let a = yield promiseOne(value);
    let b = yield promiseTwo(a);
    return a + b;
}
function show(item) {
    console.log(item)
}
asyncRun(show, asyncFn, 12);
function promiseOne(xxx) {
    return new Promise((res, rej) => {
        res(xxx + 1);
    })
}
function promiseTwo(xxx) {
    return new Promise((res, rej) => {
        res(xxx + 1);
    })
}

更簡單的方法 async/await

上面複雜的代碼如果變成async/await要怎麼做呢

很簡單

// function* asyncFn(value) {
//     let a = yield promiseOne(value);
//     let b = yield promiseTwo(a);
//     return a + b;
// }
function promiseOne(xxx) {
    return new Promise((res, rej) => {
        res(xxx + 1);
    })
}
function promiseTwo(xxx) {
    return new Promise((res, rej) => {
        res(xxx + 1);
    })
}
async function asyncFn(value) {
    let a = await promiseOne(value);
    let b = await promiseTwo(a);
    return a + b;
}
asyncFn(12).then((res)=>{
    console.log(res)
});

通過上面的例子,我們可以發現其實async/await本質上其實是 generator的一個語法糖

await就是yield , async 的作用就是將函數編程語法糖

如果背的話很簡答兩條規則:

  1. await後面必須是promise函數
  2. async 標記過得函數執行後返回的promise

通過這種方法就可以簡單的實現異步了

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章