js非同步發展歷史與Promise原理分析

標記層疊樣式球發表於2019-02-16

關於非同步

所謂”非同步”,簡單說就是一個任務不是連續完成的,可以理解成該任務被人為分成兩段,先執行第一段,然後轉而執行其他任務,等做好了準備,再回過頭執行第二段。

比如,有一個任務是讀取檔案進行處理,任務的第一段是向作業系統發出請求,要求讀取檔案。然後,程式執行其他任務,等到作業系統返回檔案,再接著執行任務的第二段(處理檔案)。這種不連續的執行,就叫做非同步。

相應地,連續的執行就叫做同步。由於是連續執行,不能插入其他任務,所以作業系統從硬碟讀取檔案的這段時間,程式只能乾等著。

簡單的說同步就是大家排隊工作,非同步就是大家同時工作。如果你還不太明白非同步與同步,多看看JS基礎的文章。

非同步的發展歷史

1.CallBack寫法

CallBack意為“回撥函式”,即非同步操作執行完後觸發執行的函式,例如:

$.get("http://api.xxxx.com/xxx",callback);

當請求完成時就會觸發callback函式。

callback可以完成非同步操作,但是經歷過JQuery時代的人應該都對某一種需求折磨過,舉個例子:專案要求前端ajax請求後端介面列表型別名稱,然後在用型別名稱ajax請求列表id,在用id請求列表具體內容,最後程式碼大概是這樣的

$.ajax({
    url: "type",
    data:1,
    success: function (a) {
        $.ajax({
            url: "list",
            data:a,
            success: function (b) {
                $.ajax({
                    url: "content",
                    data:b,
                    success: function (c) {
                        console.log(c)
                    }
                })
            }
        })
    }
})

這是是單純的巢狀程式碼,如若再加上業務程式碼,程式碼可讀性可想而知,如果是開發起來還好,但是後期的維護和修改的難度足以讓人瘋掉。這就是那個JQuery時代的“回撥地獄”問題。

2.Promise

為了解決“回撥地獄”問題,提出了Promise物件,並且後來加入了ES6標準,Promise物件簡單說就是一個容器,裡面儲存著某個未來才會結束的事件(通常是一個非同步操作)的結果。從語法上說,Promise 是一個物件,從它可以獲取非同步操作的訊息。Promise 提供統一的 API,各種非同步操作都可以用同樣的方法進行處理。

const promise = new Promise(function(resolve, reject) {
  // ... some code

  if (/* 非同步操作成功 */){
    resolve(value);
  } else {
    reject(error);
  }
});

Promise建構函式接受一個函式作為引數,該函式的兩個引數分別是resolve和reject。它們是兩個函式,由 JavaScript 引擎提供,不用自己部署。

resolve函式的作用是,將Promise物件的狀態從“未完成”變為“成功”(即從 pending 變為 resolved),在非同步操作成功時呼叫,並將非同步操作的結果,作為引數傳遞出去;reject函式的作用是,將Promise物件的狀態從“未完成”變為“失敗”(即從 pending 變為 rejected),在非同步操作失敗時呼叫,並將非同步操作報出的錯誤,作為引數傳遞出去。

Promise例項生成以後,可以用then方法分別指定resolved狀態和rejected狀態的回撥函式。

promise.then(function(value) {
  // success
}, function(error) {
  // failure
});

then方法可以接受兩個回撥函式作為引數。第一個回撥函式是Promise物件的狀態變為resolved時呼叫,第二個回撥函式是Promise物件的狀態變為rejected時呼叫。其中,第二個函式是可選的,不一定要提供。這兩個函式都接受Promise物件傳出的值作為引數。

這樣採用 Promise,解決“回撥地獄”問題,比如連續讀取多個檔案,寫法如下。

var readFile = require(`fs-readfile-promise`);

readFile(fileA)
.then(function (data) {
  console.log(data.toString());
})
.then(function () {
  return readFile(fileB);
})
.then(function (data) {
  console.log(data.toString());
})
.catch(function (err) {
  console.log(err);
});

可見這種寫法要比CallBack寫法直觀的多。但是,有沒有更好的寫法呢?

3.Generator 函式

Genrator 函式要用* 來比標識,yield關鍵字表示暫停。將函式分割出好多個部分,呼叫一次next就會繼續向下執行。返回結果是一個迭代器,迭代器有一個next方法。

function* read() {
    console.log(1);
    let a = yield `123`;
    console.log(a);
    let b = yield 9
    console.log(b);
    return b;
}
let it = read();
console.log(it.next(`213`)); // {value:`123`,done:false}
console.log(it.next(`100`)); // {value:9,done:false}
console.log(it.next(`200`)); // {value:200,done:true}
console.log(it.next(`200`)); // {value:200,done:true}

yield後面跟著的是value的值,yield等號前面的是我們當前呼叫next傳進來的值,並且第一次next傳值是無效的。

處理非同步的時候Generator和Promise搭配使用

let bluebird = require(`bluebird`);
let fs = require(`fs`);
let read = bluebird.promisify(fs.readFile);//將readFile轉為Promise物件的例項
function* r() {
    let content1 = yield read(`./2.promise/1.txt`, `utf8`);
    let content2 = yield read(content1, `utf8`);
    return content2;
}

這樣看起來是我們想要的樣子,但是隻寫成這樣還不行,想得到r()的結果還要對函式進行處理

function co(it) {
    return new Promise(function (resolve, reject) {
        function next(d) {
            let { value, done } = it.next(d);
            if (!done) {
                value.then(function (data) { // 2,txt
                    next(data)
                }, reject)
            } else {
                resolve(value);
            }
        }
        next();
    });
}
co(r()).then(function (data) {
    console.log(data)//得到r()的執行結果
})

這樣的處理方式顯然很麻煩,並不是我們想要,我們想要直觀的寫起來就就像同步函式,而且簡便的方式處理非同步。有這樣的方法嗎?

4.async-await函式

ES2017 標準引入了 async 函式,使得非同步操作變得更加方便。

async 函式是什麼?一句話,它就是 Generator 函式的語法糖。

let bluebird = require(`bluebird`);
let fs = require(`fs`);
let read = bluebird.promisify(fs.readFile);

async function r(){
    try{
        let content1 = await read(`./2.promise/100.txt`,`utf8`);
        let content2 = await read(content1,`utf8`);
        return content2;
    }catch(e){ // 如果出錯會catch
        console.log(`err`,e)
    }
}

一比較就會發現,async函式就是將 Generator 函式的星號(*)替換成async,將yield替換成await,僅此而已。

async函式返回的是promise

r().then(function(data){
    console.log(data);
},function(err){

})

至此,async-await函式已經可以我們滿意,以後會不會出現更優秀的方案?以我們廣大程式群體的創造力,相信一定會有的。

Promise原理分析

async-await函式其實只是Generator函式的語法糖,而Generator函式的實現方式也是要基於Promise,所以我們隊Promise的實現原理進行分析。

Promise物件有以下幾種狀態:

  • pending: 初始狀態, 既不是 fulfilled 也不是 rejected.
  • fulfilled: 成功的操作.
  • rejected: 失敗的操作.

在上面瞭解了Promise的基本用法後,我們先將Promise的框架搭起來

function Promise(executor) { // executor是一個執行函式
    let self = this;
    self.status = `pending`;
    self.value = undefined; // 預設成功的值
    self.reason = undefined; // 預設失敗的原因
    self.onResolvedCallbacks = []; // 存放then成功的回撥
    self.onRejectedCallbacks = []; // 存放then失敗的回撥
    function resolve(value) { // 成功狀態
        
    }
    function reject(reason) { // 失敗狀態
        
    }
    try {
        executor(resolve, reject)
    } catch (e) { // 捕獲的時候發生異常,就直接失敗了
        reject(e);
    }
}

Promise.prototype.then = function (onFulfilled, onRjected) {
//then方法
})

接下來當呼叫成功狀態resolve的時候,會改變狀態,執行回撥函式:

function resolve(value) { // 成功狀態
        if (self.status === `pending`) {
            self.status = `resolved`;
            self.value = value;
            self.onResolvedCallbacks.forEach(function (fn) {
                fn();
            });
        }
    }

reject函式同理

function reject(reason) { // 失敗狀態
        if (self.status === `pending`) {
            self.status = `rejected`;
            self.reason = reason;
            self.onRejectedCallbacks.forEach(function (fn) {
                fn();
            })
        }
    }

接下來我們完成then函式

Promise.prototype.then = function (onFulfilled, onRjected) {
    let self = this;
    let promise2; //返回的promise
    if (self.status === `resolved`) {
        promise2 = new Promise(function (resolve, reject) {
            
        })
    }
    if (self.status === `rejected`) {
        promise2 = new Promise(function (resolve, reject) {
            
        })
    }
    // 當呼叫then時可能沒成功 也沒失敗
    if (self.status === `pending`) {
        promise2 = new Promise(function (resolve, reject) {

        })
    }
    return promise2;
}

Promise允許鏈式呼叫,所以要返回一個新的Promise物件promise2

Promise.prototype.then = function (onFulfilled, onRjected) {
    //成功和失敗預設不穿給一個函式
    onFulfilled = typeof onFulfilled === `function` ? onFulfilled : function (value) {
        return value;
    }
    onRjected = typeof onRjected === `function` ? onRjected : function (err) {
        throw err;
    }
    let self = this;
    let promise2; //返回的promise
    if (self.status === `resolved`) {
        promise2 = new Promise(function (resolve, reject) {
            setTimeout(function () {
                try {
                    let x = onFulfilled(self.value);
                    // x可能是別人promise,寫一個方法統一處理
                    resolvePromise(promise2, x, resolve, reject);
                } catch (e) {
                    reject(e);
                }
            })
        })
    }
    if (self.status === `rejected`) {
        promise2 = new Promise(function (resolve, reject) {
            setTimeout(function () {
                try {
                    let x = onRjected(self.reason);
                    resolvePromise(promise2, x, resolve, reject);
                } catch (e) {
                    
                    reject(e);
                }
            })

        })
    }
    // 當呼叫then時可能沒成功 也沒失敗
    if (self.status === `pending`) {
        promise2 = new Promise(function (resolve, reject) {
            // 此時沒有resolve 也沒有reject
            self.onResolvedCallbacks.push(function () {
                setTimeout(function () {
                    try {
                        let x = onFulfilled(self.value);
                        resolvePromise(promise2, x, resolve, reject);
                    } catch (e) {
                        reject(e)
                    }
                })
            });
            self.onRejectedCallbacks.push(function () {
                setTimeout(function () {
                    try {
                        let x = onRjected(self.reason);
                        resolvePromise(promise2, x, resolve, reject);
                    } catch (e) {
                        reject(e);
                    }
                })
            });
        })
    }
    return promise2;
}

在promise2內部定義一個變數x為回撥函式的返回值,由於返回值可能會有多種可能的情況,所以我們定義一個resolvePromise函式統一處理

返回值可以分為

  • promise返回自己 (報錯迴圈引用)
  • 返回promise物件 (根據promise物件呼叫成功或失敗回撥函式)
  • 返回普通值 (呼叫成功回撥函式傳入返回值)
  • 報錯 (呼叫失敗回到傳入錯誤)
function resolvePromise(promise2, x, resolve, reject) {
    if (promise2 === x) { 
        return reject(new TypeError(`迴圈引用了`))
    }
    // 判定x是不是一個promise,promise應該是一個物件
    let called; // 表示是否呼叫過成功或者失敗
    if (x !== null && (typeof x === `object` || typeof x === `function`)) {
        try { // {then:1}
            let then = x.then;
            if (typeof then === `function`) {
                // 成功
                then.call(x, function (y) {
                    if (called) return
                    called = true
                    // y可能還是一個promise,在去解析直到返回的是一個普通值
                    resolvePromise(promise2, y, resolve, reject)
                }, function (err) { //失敗
                    if (called) return
                    called = true
                    reject(err);
                })
            } else {
                resolve(x)
            }
        } catch (e) {
            if (called) return
            called = true;
            reject(e);
        }
    } else { // 說明是一個普通值
        resolve(x); // 呼叫成功回撥
    }
}

如果返回值為物件或函式,且有then方法,那我們就認為是一個promise物件,去呼叫這個promise進行遞迴,直到返回普通值呼叫成功回撥。

最後,再加上一個catch方法,很簡單

Promise.prototype.catch = function (callback) {
    return this.then(null, callback)
}

這些就是promise的主要功能的原理,附上完整程式碼

function Promise(executor) { // executor是一個執行函式
    let self = this;
    self.status = `pending`;
    self.value = undefined; // 預設成功的值
    self.reason = undefined; // 預設失敗的原因
    self.onResolvedCallbacks = []; // 存放then成功的回撥
    self.onRejectedCallbacks = []; // 存放then失敗的回撥
    function resolve(value) { // 成功狀態
        if (self.status === `pending`) {
            self.status = `resolved`;
            self.value = value;
            self.onResolvedCallbacks.forEach(function (fn) {
                fn();
            });
        }
    }
    function reject(reason) { // 失敗狀態
        if (self.status === `pending`) {
            self.status = `rejected`;
            self.reason = reason;
            self.onRejectedCallbacks.forEach(function (fn) {
                fn();
            })
        }
    }
    try {
        executor(resolve, reject)
    } catch (e) { 
        reject(e);
    }
}
function resolvePromise(promise2, x, resolve, reject) {
    if (promise2 === x) { 
        return reject(new TypeError(`迴圈引用了`))
    }
    let called; 
    if (x !== null && (typeof x === `object` || typeof x === `function`)) {
        try { 
            let then = x.then;
            if (typeof then === `function`) {
                then.call(x, function (y) {
                    if (called) return
                    called = true
                    resolvePromise(promise2, y, resolve, reject)
                }, function (err) { //失敗
                    if (called) return
                    called = true
                    reject(err);
                })
            } else {
                resolve(x)
            }
        } catch (e) {
            if (called) return
            called = true;
            reject(e);
        }
    } else { 
        resolve(x); 
    }
}
Promise.prototype.then = function (onFulfilled, onRjected) {
    onFulfilled = typeof onFulfilled === `function` ? onFulfilled : function (value) {
        return value;
    }
    onRjected = typeof onRjected === `function` ? onRjected : function (err) {
        throw err;
    }
    let self = this;
    let promise2; 
    if (self.status === `resolved`) {
        promise2 = new Promise(function (resolve, reject) {
            setTimeout(function () {
                try {
                    let x = onFulfilled(self.value);
                    resolvePromise(promise2, x, resolve, reject);
                } catch (e) {
                    reject(e);
                }
            })
        })
    }
    if (self.status === `rejected`) {
        promise2 = new Promise(function (resolve, reject) {
            setTimeout(function () {
                try {
                    let x = onRjected(self.reason);
                    resolvePromise(promise2, x, resolve, reject);
                } catch (e) {
                    
                    reject(e);
                }
            })

        })
    }

    if (self.status === `pending`) {
        promise2 = new Promise(function (resolve, reject) {
            self.onResolvedCallbacks.push(function () {
                setTimeout(function () {
                    try {
                        let x = onFulfilled(self.value);
                        resolvePromise(promise2, x, resolve, reject);
                    } catch (e) {
                        reject(e)
                    }
                })
            });
            self.onRejectedCallbacks.push(function () {
                setTimeout(function () {
                    try {
                        let x = onRjected(self.reason);
                        resolvePromise(promise2, x, resolve, reject);
                    } catch (e) {
                        reject(e);
                    }
                })
            });
        })
    }
    return promise2;
}

Promise.prototype.catch = function (callback) {
    return this.then(null, callback)
}

相關文章