理解js異步編程

Promise

  1. 背景
    • javascript語言的一大特色就是單線程,在某個特定的時刻只有特定的代碼可以被執行,並阻塞其它的代碼,也就是說,同一個時間只能作一件事。
    • 怎麼作到異步編程?回調函數。直到nodejs的出現,開始將回調模式的異步編程機制發揮的淋漓盡致,這種機制開始在前端變得很是流行,可是慢慢也體現出了回調函數在錯誤處理嵌套上的反作用。
    • 由於存在上面的不足,因此異步解決方案一直在發展中,從 callback => promise => generator => async/await => rx => .....
  2. 簡單瞭解event loopjavascript

    +javascript上, 全部同步任務都在主線程上執行,也能夠理解爲存在一個「執行棧」。
    • 主線程外,還有一個「任務隊列」,任務隊列的做用,就在等待異步任務的結果,只要異步任務有了運行結果,就會加入到「任務隊列」中。
    • 一旦執行棧中全部同步任務執行完畢,就從 任務隊列 中讀取「任務」加入到「執行棧」中。
    • 主線程不斷的在循環上面的步驟。前端

      (function() {
      
        console.log('這是開始');
      
        setTimeout(function cb() {
          console.log('這是來自第一個回調的消息');
          setTimeout(function cb3() {
            console.log("這是來自第三個回調的消息");
          })
        });
      
        console.log('這是一條消息');
      
        setTimeout(function cb1() {
          console.log('這是來自第二個回調的消息');
          setTimeout(function cb3() {
            console.log("這是來自第四個回調的消息");
          })
        });
      
        console.log('這是結束');
      
      })();
  3. 什麼是Promisejava

    • Promise代指那些還沒有完成的一些操做,可是其在將來某個時間會返回某個特定的結果,成功或者失敗。
    • 語法上來說,promise是一個對象,表明一個未知的值,當值返回時,Promise老是處於下面的三種狀態之一:
      • pending:等待。
      • resolved: 已完成。
      • rejected: 已拒絕。
    • 狀態只可能從pening -> resolved | pending -> rejected,而且一旦改變,就不會再發生變化了。
    • promise對象必須是thenable的,並且then必須返回一個promise。
  4. 爲何要使用Promisenode

  • 代碼看起來更符合邏輯,可讀性更強。
  • 解決回調地獄
  • 更好的捕獲錯誤
  1. 舉幾個栗子
  • 最簡單的Promisenpm

    這裏看起來很簡單,但有兩點是要注意的
    1. 必定要resolve或者reject,不然你的then是永遠也執行不到的。
    2. promise的狀態必定改變後,就不再會發生變化了。
let promise = new Promise(function(resolve, reject) {
    resolve("success");
    reject("fail");
  });
 promise.then((value) => {
    console.log(value);
  }).catch((reason) => {
    console.log(reason);
  });

輸出結果編程

這個例子也充分證實了Promise只有一個狀態結果,而且是不可變的json

18a20d2f9465c419476d87785685ee0f.png

  • 經典的回調地獄api

    • 回調函數的寫法promise

      多個異步事務多級依賴,回調函數會造成多級的嵌套,代碼就會變成金字塔結構,不只可讀性不高,並且在後期的維護,調試或者重構上,都充滿了風險。併發

    doSomething(function(result) {
    doSomethingElse(result, function(newResult) {
      doThirdThing(newResult, function(finalResult) {
        console.log('Got the final result: ' + finalResult);
      }, failureCallback);
    }, failureCallback);},
    failureCallback);
  • promise的寫法

    解決了嵌套問題,thenable能夠更好的支持鏈式調用,但仍是能看到回調的影子
    更加簡便的錯誤處理

doSomething()
    .then(function(result) {
        return doSomethingElse(result);
    })
    .then(function(newResult) {
        return doThirdThing(newResult);
    })
    .then(function(finalResult) {
        console.log('Got the final result: ' + finalResult);
    })
    .catch(failureCallback);

// 配合箭頭函數

doSomething()
    .then(result => doSomethingElse(result))
    .then(newResult => doThirdThing(newResult))
    .then(finalResult => {
    console.log(`Got the final result: ${finalResult}`);
    })
    .catch(failureCallback);
  • generator寫法

    有一個暫停狀態,只有咱們激活next,纔會去執行下一個異步任務

function* fuc() {
          const result = yield doSomething()
          const newResult = yield doSomethingElse(result)
          const finalResult = yield doThirdThing(newResult)
    }
    const test = fuc()
    const result = test.next() // {value: 1, done: false}
    const newResult = test.next(result.value) // {value: 1, done: false}
    const finalResult = test.next(newResult.value) // {value: 1, done: true}
    test.next() // {value: undefined, done: true}
  • async/await的寫法

    這個語法上更加簡單,看起來更像同步任務,並且不須要關心執行狀態。
    我理解這裏只是對generator的一個包裝,裏面應該有個遞歸函數,在執行next,執行done。

async function useAsyncAwait() {
    try {
        const result = await doSomething()
        const newResult = await doSomethingElse()
        const finalResult = await doThirdThing()
        console.log('Got the final result: ' + finalResult)
    } catch (e) {
        Console.error('exception: ', e)
    }
}
  • 如何包裝promise
function getJSON(url) {
    return new Promise(function(resolve, reject){
      let xhr = new XMLHttpRequest();
      xhr.open('GET', url);
      xhr.onreadystatechange = handler;
      xhr.responseType = 'json';
      xhr.setRequestHeader('Accept', 'application/json');
      xhr.send();
      function handler() {
        if (this.readyState === this.DONE) {
          if (this.status === 200) {
            resolve(this.response);
          } else {
            reject(new Error('getJSON: `' + url + '` failed with status: [' + this.status + ']'));
          }
        }
      };
    });
  }
  
  getJSON('/posts.json')
  .then((json) =>{
    // on fulfillment
  })
  .catch((error) => {
   // on rejection
   console.log(error)
  });
  • 多個promise順序執行

    promise的then,是能夠保證按照你看到的順序執行的

getJSON('/ray/api/a/server/ping')
  .then((json) =>{
    // on fulfillment
    console.log('ping a result: ' + JSON.stringify(json));
  })
.then(() => getJSON('/ray/api/b/server/ping'))
  .then(json => {
    console.log('ping b result: ' + JSON.stringify(json)) 
  })
  .catch((error) => {
   // on rejection
   console.log(error)
  });
  
  // ping a result: {"status":"OK","serverTime":1573554742633}
 // ping b result: {"status":"OK","serverTime":1573554742667}
  • Promise 的鏈式調用

    舉一個更具體的例子,體現thenable, promise的狀態不可變

var p = new Promise(function(resolve, reject){
  resolve(1);
});
p.then(function(value){               //第一個then
  console.log(value); // 1
  return value*2;
}).then(function(value){              //第二個then
  console.log(value); // 2
}).then(function(value){              //第三個then
  console.log(value); // underfined
  return Promise.resolve('resolve'); 
}).then(function(value){              //第四個then
  console.log(value); // 'resolve'
  return Promise.reject('reject');
}).then(function(value){              //第五個then
  console.log('resolve: '+ value); // 不到這裏,沒有值
}, function(err){
  console.log('reject: ' + err);  // 'reject'
})
  • 引用一些第三方庫 好比 Bluebird

    好比
    mapSeries => 同步的執行全部異步任務,
    all => 等待併發的任務所有執行完畢,
    any => 多個異步任務中,有一個執行完畢就結束了。
    ==

npm install bluebird

import * as Promise from "bluebird";

let data = ['a', 'c', 'b', 'e', 'd']
Promise.mapSeries(data, (d) => getJSON(d) ).then((result) => {console.log(result)})

// 執行結果應該是什麼

1. 若是沒有修改代碼 => 只執行了第一個a,後面的都應該第一個a出錯,因此不繼續執行了

2. 若是將getJSON裏的reject改爲resoleve => a c b e d的出錯log會按順序打印
  • 多個promise併發執行
Promise.all([ getJSON('/ray/api/a/server/ping'), getJSON('/ray/api/b/server/ping')]).then((result) => {
    console.log('ping result: ' + JSON.stringify(result));
})

// ping result: [{"status":"OK","serverTime":1573554934072},{"status":"OK","serverTime":1573554934070}]
  • 多個promise之間的競爭

promise 沒法取消執行
new Promise 裏的任務會當即執行

const delay = new Promise((resolve, reject) => { 
    setTimeout(() => {
        console.log('timeout');
        resolve('timeout');
    }, 3000)
})

Promise.race([ getJSON('/ray/api/a/server/ping'), delay]).then((result) => {
    console.log('ping result: ' + JSON.stringify(result));
})

// 思考下這裏的timeout會不會打印
  • Promise 的穿透

promise 的then必須接收函數,不然會穿透。

// 這裏`Promise.resolve(2)`並非函數,因此上一個函數的結果會穿透到下一個
Promise.resolve(1).then(Promise.resolve(2)).then((v) => {
  console.log(v)
})

// 語法錯誤
Promise.resolve(1).then(return Promise.resolve(2)).then((v) => {
  console.log(v)
})

// 穿透
Promise.resolve(1).then(null).then((v) => {
  console.log(v)
})

// 語法錯誤
Promise.resolve(1).then(return 2).then((v) => {
  console.log(v)
})

// then會返回新的promise,而且帶上他返回的結果
Promise.resolve(1).then(() => {
  return 2
}).then((v) => {
  console.log(v)
})

當then()受非函數的參數時,會解釋爲then(null),這就致使前一個Promise的結果穿透到下面一個Promise。因此要提醒你本身:永遠給then()傳遞一個函數參數。

  • 錯誤的捕獲

    一旦捕獲到錯誤,promise的then會繼續執行
    catch會檢查promis鏈上位於它以前的每一個地方(then或者其餘異步操做)。若是在它以前還有其餘catch,那麼起點就是上一個catch。

var p1 = new Promise( function(resolve,reject){
  foo.bar();
  resolve( 1 );   
});

p1.then(
  function(value){
    console.log('p1 then value: ' + value);
  },
  function(err){
    console.log('p1 then err: ' + err);
  }
).then(
  function(value){
    console.log('p1 then then value: '+value);
  },
  function(err){
    console.log('p1 then then err: ' + err);
  }
);

輕噴!

相關文章
相關標籤/搜索