javascript非同步解決方案之promise

時光_靜好發表於2019-07-24

js非同步解決方案之promise

1. promise的定義

Promise 是非同步程式設計的一種解決方案,比傳統的解決方案——回撥函式和事件——更合理和更強大。

參考

理解:

  • 沒有非同步就不需要promise。
  • Promise本身不是非同步,只是我們去編寫非同步程式碼的一種方式

2. promise 的規範

Es6將promise納入自己規範的時候,也遵循了一個相應的標準 — ``Promise A+規範。

將其歸納為4321規範

4:4大術語 3:3種狀態 2:2種事件 1:1個物件

2.1 四大術語

  1. 解決(fulfill):指一個 promise 成功時進行的一系列操作,如狀態的改變、回撥的執行。雖然規範中用 fulfill 來表示解決,但在後世的 promise 實現多以 resolve 來指代之。

  2. 拒絕(reject):指一個 promise 失敗時進行的一系列操作。

  3. 終值(eventual value):所謂終值,指的是 promise 被解決時傳遞給解決回撥的值,由於 promise 有一次性的特徵,因此當這個值被傳遞時,標誌著 promise 等待態的結束,故稱之終值,有時也直接簡稱為值(value)。

  4. 據因(reason):也就是拒絕原因,指在 promise 被拒絕時傳遞給拒絕回撥的值。

2.2 3種狀態

  • 等待態(Pending)
  • 執行態(Fulfilled)
  • 拒絕態(Rejected)

enter description here

針對每種狀態的規範 等待態(Pending) 處於等待態時,promise 需滿足以下條件:

  • 可以遷移至執行態或拒絕態

執行態(Fulfilled)

處於執行態時,promise 需滿足以下條件:

  • 不能遷移至其他任何狀態
  • 必須擁有一個不可變的終值

拒絕態(Rejected)

處於拒絕態時,promise 需滿足以下條件:

  • 不能遷移至其他任何狀態
  • 必須擁有一個不可變的據因

2.3 2種事件

針對3種狀態,只有如下兩種轉換方向:

  • pending –> fulfilled
  • pendeing –> rejected

在狀態轉換的時候,就會觸發事件。

如果是pending –> fulfiied,就會觸發onFulFilled事件 如果是pendeing –> rejected,就會觸發onRejected事件

2.4 1個物件

就是指promise物件

3. promise的基本用法

3.1 基本用法

let p = new Promise(function (resolve,reject) {
            reject("no");
        })
        console.log('p :', p);
複製程式碼

回撥函式中的兩個引數,其作用就是用於轉換狀態:

resolve,將狀態從pending –> fullFilled reject,將狀態從pending –> rejected

3.2 then 方法

在狀態轉換的時候,就會觸發事件

如果是pending –> fulfiied,就會觸發onFulFilled事件

如果是pendeing –> rejected,就會觸發onRejected事件

針對事件的註冊,Promise物件提供了then方法,如下:

enter description here

3.3promise典型定義

3.3.1 案例1:讀取檔案操作

const fs = require("fs");

let p = new Promise(function (resolve,reject) {
    fs.readFile("03-js基礎/a.txt","utf8",(err,date)=>{
        if(err){
            reject(err);
        }else{
            resolve(date);
        }
    });
  });
  
  p.then(result=>{
      console.log('result :', result);
  }).catch(err=>{
      console.log('err :', err);
  });
複製程式碼

3.3.2 案例2:根據隨機數返回結果

let p = new Promise((resolve,reject)=>{
    setTimeout(()=>{
        let num = Math.random();
        if(num>0.5){
            resolve("success");
        }else{
            reject("fail");
        }
    },1000);
});

p.then(result=>{
    console.log('result :', result);
},err=>{
    console.log('err :', err);
})
複製程式碼

3.3.3 讀取檔案封裝

const fs = require("fs");

function readFile(file){
    return new Promise((resolve,reject)=>{
        fs.readFile("file","utf8",(err,date)=>{
            if(err){
                reject(err);
            }else{
                resolve(date);
            }
        });
    });
}

readFile("a.txt").then(result=>{
    console.log('result :', result);
},err=>{
    console.log('err :', err);
})
複製程式碼

3.4 all和race方法

all:所有 race:競賽

all和race都是Promise構造器物件的靜態方法。直接使用Promise呼叫,如下:

  • Promise.all()
  • Promise.reace()
    • 返回值都是promise物件。

當有多個非同步操作的時候,經常會有如下兩種需求:(有點類似於運算子中的 邏輯與邏輯或

  1. 確保所有的非同步操作完成之後,才進行某個操作,只要有一個失敗,就不進行

  2. 只要有一個非同步操作文章,就裡面執行某個操作。

all使用如下

enter description here

如有錯誤,如下

enter description here

reac的用法

enter description here

4. 使用第三方的Promise庫

針對第三方的promise庫,有兩個知名的庫:

  • bluebird
  • q.js

以bluebird為例,在服務端演示其用法

第一步,安裝

enter description here

第二步,使用

enter description here

4. 手寫最完整版的promise原理

可以進入Github中更有層次的學習原理,本人在github中 逐步的封裝了基礎版promise,升級版promise,完整版的promise,

Github地址:github.com/quyuandong/…

示例

javascript非同步解決方案之promise

原始碼

// 定義當前promise的狀態
const PENDING = 'PENDING'
const RESOLVED = 'RESOLVED'
const REJECTED = 'REJECTED'
//=================================================================
// promise物件
class Promise {
    constructor(exector) {

        // 初始化status為等待狀態
        this.status = PENDING;
        // 成功的值
        this.value = undefined;
        // 失敗的原因
        this.reason = undefined;

        // 儲存成功的回撥函式
        this.onResolvedCallbacks = [];
        // 儲存失敗的回撥函式
        this.onRejectedCallbacks = [];

        // 成功的處理函式
        let resolve = (value) => {
            if (this.status == PENDING) {
                this.value = value;     //儲存成功的值
                this.status = RESOLVED; //將當前的狀態修改為成功狀態
                this.onResolvedCallbacks.forEach(fn => fn())
            }
        }

        // 失敗的處理函式
        let reject = (reason) => {
            if (this.status == PENDING) {
                this.reason = reason;   //儲存失敗的原因
                this.status = REJECTED; //將當前的狀態修改為失敗狀態
                this.onRejectedCallbacks.forEach(fn => fn())
            }
        }

        // 當pormise丟擲異常時
        try {
            exector(resolve, reject)    //立即執行函式
        } catch (e) {
            reject(e)
        }
    }

    /**
     * 引數:
     * @param {*} onFulfilled   失敗狀態
     * @param {*} onRejected    成功狀態
     * 
     * 說明:
     *  promise的鏈式,可以一直.then下去,需要then返回一個promise
     *  onFulfilled與onRejected的返回值,可能是一個promise 也可能是一個普通值
     *      如果是promise,就採用它的狀態
     *      如果是普通值,這個值就作為下一個then成功的結果
     * 
     */
    then(onFulfilled, onRejected) {

        // 防止then中沒有函式 p.then().then().then()...
        onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
        onRejected = typeof onRejected === 'function' ? onRejected : err => { throw err };

        /**
         * 建立一個promise,並返回一個promise,使得可以then可以進行鏈式呼叫
         * settimeout作用:  使得該方法中,可以獲取到promise2
         * try作用: 處理onfulfilled可能丟擲一個異常
         */
        let promise2 = new Promise((resolve, reject) => {

            // 成功狀態的處理方法-------同步操作(執行器中沒有非同步程式碼)
            if (this.status == RESOLVED) {
                setTimeout(() => {
                    try {
                        let x = onFulfilled(this.value);// x可能是promise 也可能是普通值
                        // 假如x是promise(有自己的狀態),需要讓pormise2擁有x同樣的狀態
                        resolvePromise(promise2, x, resolve, reject); // 處理promise2和x的關係
                    } catch (e) {
                        reject(e)
                    }
                }, 0);
            }

            // 失敗狀態的處理方法-------同步操作(執行器中沒有非同步程式碼)
            if (this.status === REJECTED) {
                setTimeout(() => {
                    try {
                        let x = onRejected(this.reason)
                        resolvePromise(promise2, x, resolve, reject);
                    } catch (e) {
                        reject(e)
                    }
                }, 0)
            }


            // 等待狀態,將成功與失敗的的操作進行儲存-------同步操作(執行器中有非同步程式碼)
            if (this.status === PENDING) {
                this.onResolvedCallbacks.push(() => {
                    setTimeout(() => {
                        try {
                            let x = onFulfilled(this.value)
                            resolvePromise(promise2, x, resolve, reject);
                        } catch (e) {
                            reject(e)
                        }
                    }, 0)
                });

                this.onRejectedCallbacks.push(() => {
                    setTimeout(() => {
                        try {
                            let x = onRejected(this.reason)
                            resolvePromise(promise2, x, resolve, reject);
                        } catch (e) {
                            reject(e)
                        }
                    }, 0)
                })
            }

        })

        return promise2;
    }
}


//=================================================================
// 處理promise2和x的關係 ,x是onFulfilled或onRejected的返回值
function resolvePromise(promise2, x, resolve, reject) {

    // 如果 x === promise2,則是會造成迴圈引用,自己等待自己完成,則報“迴圈引用”錯誤
    if (promise2 === x) {
        return reject(new TypeError('my Chaining cycle detected for promise'))
    }

    // 防止多次呼叫
    let called;

    // x是一個物件(不包含null)或是一個函式時
    if (x !== null && (typeof x === "object" || typeof x === "function")) {

        try {
            // 有可能x上會被手動地加上一個then屬性
            let then = x.then;

            if (typeof then === 'function') {   // x是一個promise時

                // 相當於 x.then(y=>{},r=>{})
                then.call(x, y => {
                    if (called) return;
                    called = true;

                    // 遞迴,解決返回值裡面扔是promise的問題
                    resolvePromise(promise2, y, resolve, reject)
                }, r => {
                    if (called) return;
                    called = true;

                    // 採用promise失敗結果向下傳遞
                    reject(r)
                })
            } else {    // 此時x是一個物件,即返回的是一個物件
                resolve(x)
            }
        } catch (e) {   //當x丟擲一個異常時
            if (called) return;
            reject(e)
        }
    } else {  //x 是一個普通的值
        resolve(x)
    }
}

//=================================================================
// resolve靜態方法
Promise.resolve = function (val) {
    return new Promise((resolve, reject) => {
        resolve(val)
    });
}


//=================================================================
// reject靜態方法
Promise.resolve = function (val) {
    return new Promise((resolve, reject) => {
        reject(val)
    });
}


//=================================================================
// race靜態方法
Promise.race = function (promises) {
    return new Promise((resolve, reject) => {
        for (let i = 0; i < promises.length; i++) {
            promises[i].then(resolve, reject)
        }
    });
}


//=================================================================
// all方法(獲取所有的promise,都執行then,把結果放到陣列,一起返回)
Promise.all = function (promises) {
    let arr = [];
    let y = 0;
    function processData(index, data) {
        arr[i] = data;
        y++;
        if (y == promises.length) {
            return arr;
        };
    };

    return new Promise((resolve, reject) => {
        for (let i = 0; i < promises.length; i++) {
            promises[i].then(data => {
                processData(i, data)
            }, reject)
        }
    })
}

//================================================================
// 
/**
 * 驗證promise的正確性
 * 1,先在後面加上下述程式碼
 * 2,npm 有一個promises-aplus-tests外掛 npm i promises-aplus-tests -g 可以全域性安裝
 * 3,命令列 promises-aplus-tests [js檔名] 即可驗證
 */


// 目前是通過他測試 他會測試一個物件
// 語法糖
Promise.defer = Promise.deferred = function () {
    let dfd = {}
    dfd.promise = new Promise((resolve,reject)=>{
      dfd.resolve = resolve;
      dfd.reject = reject;
    });
    return dfd;
  }


//=================================================================
// 匯出一個promise
module.exports = Promise;


/**
 * 注意:當在promise中出現非同步的時候,必須先將then中成功和失敗的回撥函式
 *      先進行儲存,當promise中的狀態發生改變的時候,才再進行執行
 */
複製程式碼

相關文章