淺談JavaScript非同步發展與Promise/A+原理實現

我就是近視怎麼了發表於2018-03-22

首先一提到Promise 我們首先想到的就是非同步程式設計。非同步程式設計在JavaScript中我們最早接觸的是callback形式的非同步,callback形式的非同步程式設計最大的特點就是地獄式回撥巢狀,一旦巢狀次數過多,就很容易使我們的程式碼難以理解和維護。而Permise的出現是為了更好的解決JavaScript中非同步程式設計的問題。

js中非同步的方式有哪些?

1.回撥函式callback形式:

    $.get(URL,function(res) {
        if(res) {
            $.get(URL,function(res) {
                if(res) {
                    $.get(URL,function(res) {
                        if(res) {
                        }
                    });
                }
            });
        }
    });
複製程式碼
這就是傳說中的回撥地獄!!!  
複製程式碼

2.ES6 Promise 物件:

let p = new Promise(function (resolve, reject) {
    resolve('第一次成功了')
});
p.then(function (res) { //第一次promise的結果
    console.log(res)
    let next_p = new Promise(function (resolve, reject) {
        reject('第二次失敗功了')
    });
    return next_p
}, function (err) {
    console.log(err)
}).then(function (res) { //第二次permise的結果
    console.log(res)
    
}, function (err) {
    console.log(err)
})
複製程式碼
上面是簡單的一個 es6 promise物件的用法 程式碼結構上來看,並沒有像 callback回撥那樣出現回撥地獄的形式,而是常見的鏈式呼叫,(如jquery)。
複製程式碼

3.ES6 Generator 函式 :

  Generator 函式有多種理解角度。語法上,首先可以把它理解成,Generator 函式是一個狀態機,封裝了多個內部狀態.  
  執行 Generator 函式會返回一個遍歷器物件,也就是說,Generator 函式除了狀態機,還是一個遍歷器物件生成函式。返回的遍歷器物件,  
可以依次遍歷 Generator 函式內部的每一個狀態。  
  形式上,Generator 函式是一個普通函式,但是有兩個特徵:
  一是,function關鍵字與函式名之間有一個星號;  
  二是,函式體內部使用yield表示式,定義不同的內部狀態(yield在英語裡的意思就是“產出”)
複製程式碼
function* read() {
    console.log(1);
    let a = yield '第一次';
    console.log(a);
    let b = yield '第二次'
    console.log(b);
    return b;
}
let it = read();

console.log(it.next('111')); // {value:'第一次',done:false}
console.log(it.next('222')); // {value:'第二次',done:false}
console.log(it.next('333')); // {value:'333',done:true}
複製程式碼
Generator 應用場景 主要還是要與promise 配合一起使用  
Generator 和 co:  
nodejs出現了 co 模組,它基於 ES6 的 generator 和 yield ,讓我們能用同步的形式編寫非同步程式碼。  
複製程式碼
co(function *() {
    var data = yield $.get('/api/data');
    console.log(data);
    var user = yield $.get('/api/user');
    console.log(user);
    var products = yield $.get('/api/products');
    console.log(products);
});
複製程式碼
以上的 Promise 和 generator 最初創造它的本意都不是為了解決非同步流程控制。其中 Promise 是一種程式設計思想,用於“當xx資料準備完畢,  
then執行xx動作”這樣的場景,不只是非同步,同步程式碼也可以用 Promise。而 generator 在 ES6 中是迭代器生成器,被 TJ 創造性的拿來做非同步流程  
控制了
複製程式碼

4.ES7 async / await 語法糖

async / await co + generator的語法糖  
async / await 解決的問題有哪些?  
1.回撥地獄  
2.併發執行非同步,在同一時刻同步返回結果 Promise.all()  
3.解決了返回值的問題  
4.可以實現程式碼的try/catch;
複製程式碼
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函式返回的是promise,
r().then(function(res){
    console.log(res);
},function(err){
    console.log(err);
})
複製程式碼

Promise 扮演著一個承上啟下的作用,非常關鍵,我們動手寫一個自己的Promise!

                    ---->(fulfilled)成功態 --->resolve();
                    |
    new Promise ----(pending)  初始化為等待態
                    |  
                    ---->(rejected)失敗態 --->reject();
複製程式碼
function Promise(executor) {
    let _this = this;
    //promise的 三種狀態
    _this.status = 'pending'; //等待態 初始值 例項化promise一開始時是等待狀態 唯一性
    _this.value = undefined;  //成功態 表示成功時要傳的值 初始預設值
    _this.error = undefined; //失敗態 表示失敗時要傳的值 
    _this.onFufilledCallbacks = []; // 存放then成功的回撥函式
    _this.onRejectedCallbacks = []; // 存放then失敗的回撥函式
    //成功的方法
    function resolve(value) {    
        if (_this.status === 'pending') {
            _this.status = 'fulfilled';  //當呼叫 resolve()時改為成功態 
            _this.value = value;
            //遍歷 存放then成功的回撥函式 並一次呼叫
            _this.onFufilledCallbacks.forEach(function (itemFn) {
                itemFn();
            });
        };
    };
    //失敗的方法
    function reject(error) {     
        if (_this.status === 'pending') {
            _this.status = 'rejected'; //當呼叫 reject()時改為失敗態
            _this.error = error;
            _this.onRejectedCallbacks.forEach(function (itemFn) {
                itemFn();
            });
        }

    };
    //處理 例項化時 throw new err('錯誤') 需要走reject();!!!
    try {
        executor(resolve, reject); //executor執行器,包含兩個引數,分別是resolve() 和reject(),new Promise這個executor就會立即執行
    } catch (err) { //捕獲時發生異常,就直接失敗
        reject(err)
    }
}
function resolvePromise(promise2, x, resolve, reject) {
    // 有可能這裡返回的x是別人的promise
    // 儘可能允許其他亂寫
    if (promise2 === x) { //這裡應該報一個型別錯誤,有問題
        return reject(new Typeerr('迴圈引用了'))
    }
    // 看x是不是一個promise,promise應該是一個物件
    let called; // 表示是否呼叫過成功或者失敗
    if (x !== null && (typeof x === 'object' || typeof x === 'function')) {
        // 可能是promise {},看這個物件中是否有then方法,如果有then我就認為他是promise了
        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 { // 說明是一個普通值1
        resolve(x); // 表示成功了
    }
}


// then方法註冊 當resolve(成功)/reject(失敗)的回撥函式
Promise.prototype.then = function (onFufilled, onRejected) {
    //成功和失敗預設不傳給一個函式
    onFufilled = typeof onFufilled === 'function' ? onFufilled : function (value) {
        return value;
    }
    onRejected = typeof onRejected === 'function' ? onRejected : function (err) {
        throw err;
    }
    let _this = this;
    let promise2; //返回的promise 處理 .then() 的鏈式呼叫 返回一個新的promise物件
    
    if (_this.status === 'fulfilled') {
        promise2 = new Promise(function (resolve, reject) {
            // 當成功或者失敗執行時有異常那麼返回的promise應該處於失敗狀態
            // x 是return的返回值 可能是一個promise 也可能是一個普通值
            setTimeout(function () { //用setTimeout來模擬非同步,真正的es6promise不是用setTimeout來實現的
                try {
                    let x = onFufilled(_this.value)
                    // x可能是別人promise,寫一個方法統一處理
                    resolvePromise(promise2, x, resolve, reject)
                } catch (err) {
                    reject(err);
                }
            });
        });

    };
    if (_this.status === 'rejected') {
        //當成功或者失敗執行時有異常時 那麼返回的 promise2應該處於失敗狀態
        promise2 = new Promise(function (resolve, reject) {
            setTimeout(function () {
                try {
                    let x = onRejected(_this.error); // x 是return的返回值 可能是一個promise 也可能是一個普通值
                    resolvePromise(promise2, x, resolve, reject)
                } catch (err) {
                    reject(err);
                }
            });
        });
    };
    //當呼叫 then 時 有可能沒成功也沒失敗 還是pending 需要把回撥先存起來
    if (_this.status === 'pending') {
        promise2 = new Promise(function (resolve, reject) {
            _this.onFufilledCallbacks.push(function () {
                setTimeout(function () {//用setTimeout來模擬非同步,真正的es6promise不是用setTimeout來實現的
                    try {
                        let x = onFufilled(_this.value)
                        resolvePromise(promise2, x, resolve, reject)
                    } catch (err) {
                        reject(err)
                    }
                });
            });
            _this.onRejectedCallbacks.push(function () {
                setTimeout(function () {
                    try {
                        let x = onRejected(_this.error);
                        resolvePromise(promise2, x, resolve, reject)
                    } catch (err) {
                        reject(err)
                    }
                });
            });
        });
    };
    return promise2
}

複製程式碼
使用自己的封裝的promise:
複製程式碼

let p = new Promise(function(resolve,reject) {
    
        resolve('success');
   
});
p.then(function(data) {
    console.log('成功:',data)
    return 'zhanghw'
},function(err) {
    console.log('失敗:',err)
}).then(function(data) {
    console.log(data)
},function(err) {
    console.log(err)
})
複製程式碼
列印結果:  
成功: success
zhanghw
複製程式碼

相關文章