Javascript 在ES6新增了大量非常實用的功能,其中重要一項就是Promsie,讓我們可以很直覺的處理非同步,在以前如果我們需要同時發出多個非同步請求,就必須在每次調用function 時,一起在參數帶回callback的function,重複了幾次就變成了波動拳。

接下來會用 promise 處理 callback hell,還有建立一個簡易的 promise,幫助我們理解promise。

javascript callback hell

簡易的 Promise

複雜專案可能會出現的波動拳,這畫面我真實有看過…,假設換成用promise的話,就可以很輕鬆直覺處理掉,首先我們先建立一個簡單的ajax function sample code,下面會用es6來寫,希望能在整個流程中,讓你了解es6的方便。

宣告一個getData arrow function,裡面包含XMLHttpRequest,我們監聽onreadystatechange,當整個成功取得資料,就調用resolve來進行callback把資料放進resolve function傳遞,當取得資料失敗就調用reject function來傳遞資料。

當我們new一個promise的同時,我們callback function 是帶入 function(resolve, reject){resolve or reject},讓內部promise function被我們用resolve或是reject調用。

  • promise 三種狀態
    擱置(pending):初始狀態,不是 fulfilled 與 rejected。
    實現(fulfilled):表示操作成功地完成。
    拒絕(rejected):表示操作失敗了。

promise 會等待佇列 pending狀態,等到被resolve觸發fulfilled,就會開始回調then,或是被reject觸發catch。

then 以及 catch 都會回傳一個promise,也就是說可以.then(()=>{}).then(()=>{}).then(()=>{})除非有錯誤產生,否則會往下調用下去。.catch(()=>{}).catch(()=>{}).catch(()=>{})則是當javascript有錯誤發生,會開始向下catch,直到沒錯誤為止才會調用catch function,同時因為沒錯誤所以會停住。

  • 非同步function 範例
    // declare arrow function return Promise
    // ** new Promise to inherit Promise instance **
    const getData = (url) => new Promise((resolve, reject) => {
    // create http request
    const xhttp = new XMLHttpRequest();
    xhttp.onreadystatechange = () => {
    console.log(xhttp.readyState)
    if (xhttp.readyState === 4 ) {
    if (xhttp.status === 200) {
    // resolve will trigger Promise then callback
    console.log(JSON.parse(xhttp.response));
    resolve(JSON.parse(xhttp.response));
    } else {
    // reject will trigger Promise catch callback
    reject(xhttp.statusText);
    }
    }
    };
    xhttp.open("GET", url, true);
    xhttp.send();
    });

    getData('https://jsonplaceholder.typicode.com/todos/1')
    .then(res => {
    // destructuring object
    const { id, title, completed } = res;
    const html = `<div class="item">
    <p>Id: ${id}</p>
    <div>Title: ${title}</div>
    <div>Completed: ${completed}</div>
    </div>`;
    const newNode = document.createElement('div');
    newNode.innerHTML = html;
    document.querySelector('#list').appendChild(newNode);
    }
    )
    // it will run when promise Reject or
    // in then function appear javascript error
    .catch(res => {
    console.log(res);
    alert(`Something Error ,because ${res}`);
    });

codepen promise demo

Promise 解決 callback hell

那如果我們要繼續拉第二筆資料 /todos/2,這時候就能展現promise方便了,當拉完資料後,再回傳一個promise,再用then catch去接受回傳值,反覆下去延伸。

這樣的寫法優點是比起以往依賴callback更直覺,另外每次都分同步取資料都可能會失敗。也很容易針對每個段點做不同的錯誤處理。

  • add more callback
    ...
    getData('https://jsonplaceholder.typicode.com/todos/1')
    .then(res => {
    appendItem(res);
    // return getData promise
    return getData('https://jsonplaceholder.typicode.com/todos/2')
    }
    )
    .catch(res => {
    console.log('top', res);
    })
    // it will start next promise then catch
    .then(res => {
    appendItem(res);
    return getData('https://jsonplaceholder.typicode.com/todos/3')
    }
    ).catch(res => {
    console.log('middle', res);
    })
    .then(res => {
    appendItem(res);
    }
    ).catch(res => {
    console.log('bottom', res);
    })

    // append html function
    const appendItem = (res) => {
    // prevent get null or undefined trigger .catch
    if (!res) {
    return;
    }
    const { id, title, completed } = res;
    const html = `<div class="item">
    <div>Id: ${id}</div>
    <div>Title: ${title}</div>
    <div>Completed: ${completed}</div>
    </div>`;
    const newNode = document.createElement('div');
    newNode.innerHTML = html;
    document.querySelector('#list').appendChild(newNode);
    }

codepen promise demo

實現 promise

promise就像是個魔術,直到es6以前都很難處理好分同步處理,我們來試著做一個只單純帶有then catch簡易的promise,來幫助我們更了解promise。

先來解讀一下promise,他是依賴resolve、reject function調用的,直接 new Promise(()=>{}),可以看到promise內部的狀態,有status、value、then、catch、finally,這邊就先不理會finally。

  • Promise native prototype
    Promise {<pending>}
    __proto__: Promise
    catch: ƒ catch()
    constructor: ƒ Promise()
    finally: ƒ finally()
    then: ƒ then()
    Symbol(Symbol.toStringTag): "Promise"
    __proto__: Object
    [[PromiseStatus]]: "pending"
    [[PromiseValue]]: undefined

首先先用es6 class 建立一個 promise,依照promise內部code,我們也建立內部的變數 status 以及 value,status是讓我們判斷pending 或是fullfill reject狀態,value則是用來接帶進來的值。

這邊比較容易看不懂的是 constructor(callback),這個callback指的是 new Promise( (res,rej)=>{ res('Hello')} ),簡單講就是你帶進來的function。我們在使用promise會用到兩個function,reslove以及reject,我們也依樣畫葫蘆,依樣命名一個resolve、reject,依照(resolve,reject)順序帶入callback,帶進來讓使用者可以調用到。當我們在外部使用第一個function,就會調用到內部的reslove,如果是第二個的話則是調用到內部的reject。

距離實際的promise還缺少了then、catch,接下來再繼續實作。

  • build promise

    class promise {
    constructor(callback){
    // promise status
    this.status = 'pending';
    // create variable to store resolve or reject value
    this.value;
    // resolve is not outside resolve
    // it is use to pass callback function first function

    const resolve = res => {
    if(this.status === 'pending'){
    this.status = 'fullfilled';
    this.value = res;
    }
    }

    // reject is not outside reject
    // it is use to pass callback function second function
    const reject = res => {
    if(this.status !== 'pending'){
    this.status = 'rejected';
    this.value = res;
    }
    }

    // it's keypoint to call reslove or reject function
    // reslove or reject just assign status and value
    try {
    // this resolve is upper resolve function
    callback(resolve, reject);
    } catch (err) {
    reject(err);
    }
    }
    }
  • 加上then catch 接受回傳值

    ...
    // then will let user call to check status
    then = (success, failed) => {
    console.log(`then`)
    if(this.status === 'fulfilled'){
    return success(this.value);
    }
    if(this.status === 'rejected'){
    return failed(this.value);
    }
    }
    // it will call then function and callback second callback function
    catch = cb => {
    this.then(null,cb);
    }
    }

codepen promise build

到這裡只是很簡單的promise大致上執行邏輯而已,方便我們更好理解promise原理,要完整實現還有很多細節要處理,例如說要then 或是 catch 要return promise,還有all race finally沒有寫上去。你可以看一下更多完整功能要怎實現出來。

vkarpov15/promise.js

雖然每次用promise都很理所當然,規則已經既定是如此,但如果每次使用都能了解背後原理,能夠以不同角度來看待,我自己覺得對我來說,幫助都很大,最近正在無盡的的優化網頁效能,無限感慨中…。

如果有錯誤歡迎留言,感謝閱讀。