promise es6

what is promise

MDN 裏面有詳細的簡介
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Guide/Using_promises

1.同步和異步

我們知道js的執行環境是單線程的。所謂的單線程,是指js引擎中負責解釋和執行js代碼的線程只有一個,也就是一次只能完成一項任務,這個任務執行完才能開始下一個,它會阻止其他的任務,就是js代碼自上而下的執行,只能等待上面的代碼全部執行完事才能開始它的下面的代碼的執行。這個任務可稱爲主線程。但是實際上還有其他的線程,如事件觸發線程、ajax請求線程等。這也就引發了同步和異步的問題哦。

1.1 同步

同步模式,就是上面所說的單線程模式, 一次只能執行 一個任務,函數調用後需要等到函數執行結束,返回執行的結果,才能執行下一個任務。如果這個任務執行的時間較長,就會導致 線程阻塞

例1.1

const x = true
while(x);
console.log("don not carry me !!!")

上面的例子就是同步模式,其中的while是一個死循環,它會阻塞進程,因此console.log是在控制檯打印不出來的。
同步模式比較簡單,也比較容易編寫。但問題也顯而易見,如果請求的時間比較長,而阻塞了後面代碼的執行,體驗是很不好的。因此對於一些耗時的操作,異步模式則是更好的選擇。

1.2 異步

異步模式,即與同步模式相反,可以一起執行 多個任務,函數調用後不會立即返回執行的結果,如果任務A需要等待,可先執行任務B,等到任務A結果返回後再繼續回調。
最常見的模式就是定時器了,我們來看看下面的例子

setTimeout(function(){
        console.log('taskA,asynchronous')
    },0)
console.log('taskB,synchronous')
// while(x);

控制檯的輸出爲
taskB,synchronous
taskA,asynchronous
我們可以看到,定時器延時的時間明明爲0,但taskA還是晚於taskB執行。這是爲什麼呢???由於定時器是異步的, 異步任務會在當前腳本的所有同步任務執行完纔會執行。 如果同步代碼中含有死循環,即將上面的while的註釋去掉,那麼這個異步任務就不會執行,因爲同步任務阻塞了進程。

1.3回調函數

說到異步,就一定要說一下回調函數了。上面的例子中,setTimeout裏面的function便是回調函數。可以簡單理解爲:(執行完)回(來)調(用)的函數。
回調函數是一段可執行的代碼段,它以參數的形式傳遞給其他代碼,在合適的時間執行這段(回調函數)的代碼。

回調函數不僅可以用於異步調用,一般同步的場景也可以用回調。在同步調用下,回調函數一般是最後執行的。而在異步調用下,可能一段時間後執行或者步執行。

//同步回調
    var fun1 = function(callback){
        //do something
        //這個地方一定要加分號否則會報錯
        console.log('before callback');
        (callback && typeof(callback) === 'function') && callback()
        console.log('after callback')
    }

    var fun2 = function(param){
        // do something
        var start = new Date()
        while((new Date() - start) < 3000){
           
        }
        console.log('i am callback')
    }

    fun1(fun2)

輸出結果爲:
before callback
i am callback
after callback
由於是同步回調,會阻塞後面的代碼,如果fun2是個死循環,後面的代碼就不會再執行了。setTimeout是常見的異步回調,另外的一個異步回調即ajax請求。

2.爲什麼要使用Promise

說完以上的基本概念,我們就可以繼續學習Promise
上面提到,Promise對象是用於異步操作的,既然我們可以使用異步回調函數來進行異步操作,爲什麼還要引入一個promise新概念,還要花時間學習它呢,我們可以看看Promise的過人之處。利用Promise改寫異步回調。

 function sendRequest(url,param){
        return new Promise(function(resolve,reject){
            request(url,param,resolve,reject)
        })
    }

    sendRequest('test.html','').then(function(data){
        //異步操作成功後的回調
        console.log('請求成功了,這是返回的數據:',data)
    },function(error) {
        //異步操作失敗後的回調
        console.log('請求失敗了,這是返回的數據:',error)
    })

這麼一看,並沒有什麼大的區別,還比上面的異步回調複雜,需要先新建Promise再定義其回調。其實,Promise的真正強大之處在於它的多重鏈式調用,可以避免層層嵌套回調。如果我們在第一次ajax請求後,還要用它返回的結果再次請求呢???

 request('test.html','',function(data1){
        console.log('這是第一次請求成功,這是返回的數據:',data1)
        request('test.html','data1',function(data2){
            console.log('這是第二次請求成功,這是返回的數據:',data2)
            request('test.html','data2',function(data3){
                console.log('這是第三次請求成功,這是返回的數據:',data3)
            },function(error3){
                console.log('這是第三次請求失敗,這是返回的數據:',error3)
            })
        },function(error2){
            console.log('這是第二次請求失敗,這是返回的數據:',error2)

        })
    },function(error1){
        console.log('這是第一次請求失敗,這是返回的數據:',error1)
    })

上面的代碼出現了多層回調嵌套,有種暈頭轉向的感覺,編程體驗十分不好,而使用Promise,我們就可以使用then進行鏈式回調,將異步操作以同步方式操作的流程表示出來。

sendRequest('test.html','').then(function(data1){
        console.log('這是第一次請求成功,這是返回的數據:',data1)
        return sendRequest('test2.html',data1)
    }).then(function(data2){
        console.log('這是第二次請求成功,這是返回的數據:',data2)
        return sendRequest('test3.html',data2)
    }).then(function(data3){
        console.log('這是第三次請求成功,這是返回的數據:',data3)
    }).catch(function(error){
        //使用catch捕獲前面的錯誤
        console.log('這是第一次請求失敗,這是失敗的信息:',error)
    })

是不是感覺到清晰明顯一些,下面我們開始正式進入Promise的學習。

3 Promise的基本用法

3.1 基本用法

上面我們認識了promise長什麼樣,但對它用到的resolve,rejected,then,catch想必還不瞭解。

Promise對象代表一個未完成、但是預計將來會完成的操作。
它有以下三種狀態:

  • pending:初始值,不是fulfilled,也不是rejected
  • fulfilled:代表操作成功
  • rejected:代表操作失敗
    promise有兩種狀態改變的方式,既可以從pending轉變爲 fulfilled,也可以從pending轉變爲rejected。一旦狀態改變,就不變了。當狀態發生變化,promise.then綁定的函數就會被調用。
    promise一旦新建就會立即執行,無法取消。這是它的缺點之一。
var promise = new Promise(function(resolve,reject){
        if(/*異步操作成功*/){
            resolve(data)
        }else{
            reject(error)
        }
    })

類似構建對象,我們使用new來構建一個 promisepromise接受一個函數作爲參數,該函數的兩個參數分別是resolve和reject。這兩個函數就是回調函數,由js引擎提供。

resolve函數的作用:在異步操作成功時的調用,並將異步的操作結果,作爲參數傳遞出去;
reject函數的作用:在異步操作失敗時的調用,並將異步操作報出的錯誤,作爲參數傳遞出去;

promise實例生成以後,可以用then方法指定resolved狀態和reject狀態的回調函數。

promise.then(onFulfilled,onRejected);

    promise.then(function(data){
        // do something when success
    },function(error){
        // do something when fail
    })

then方法會返回一個Promise。then就是定義resolve和reject函數的,其resolve參數相當於:

function resolveFun(data){
        // data爲promise傳出的值
    }

而新建Promise中的’resolve(data)’,則相當於執行resolveFun函數。

Promise新建後就會立即執行。而then方法中指定的回調函數,將在當前腳本所有同步任務執行完纔會執行。

var promise = new Promise(function(resolve,reject){
        console.log('before resolved')
        resolve()
        console.log('after resolved')
    })

    promise.then(function(){
        console.log('resolved')
    })

    console.log('outer')

輸出順序:
before resolved
after resolved
outer
resolved

3.2基本API

.then()

語法:promise.prototype.then(onFufilled,onRejected)

對promise添加onFufilled,onRejected回調,並返回的是一個新的promise實例,且返回值將作爲參數傳入這個新的promise的resolve函數。
.catch()

語法:promise.prototype.then(onRejected)

該方法是.then(undefined,onRejected)的別名,用於指定發生錯誤時的回調函數。

 promise.then(function(data){
        console.log(success)
    }).catch(function(error){
        console.log(error)
    })

    //等同於
    promise.then(function(data){
        console.log(success)
    }).catch(undefined,function(error){
        console.log(error)
    })
    
var promise = new Promise(function(resolve,reject){
        throw new Error('test')
    })
    //等同於
    var promise = new Promise(function(resolve,reject){
        reject(new Error('test'))
    })

    //用catch捕獲
    promise.catch(function(error){
        console.log(error)
    })

輸出:test

reject方法的作用就等同於拋錯。
promise對象的錯誤,會一直向後傳遞,直到被捕獲。即錯誤總會被下一個catch所捕獲。then方法指定的回調函數,若拋出錯誤,也會被下一個catch捕獲。catch中也能拋錯,則需要後面的catch來捕獲。

.all()

語法:Promise.all(iterable)

該方法用於將多個Promise實例,包裝成一個新的Promise實例

 var p = Promise.all([p1,p2,p3])

Promise.all方法接受一個數組做參數,數組中的對象(p1,p2,p3)均爲promise實例,它的狀態將由這3個promise實例來決定。

  • 當p1,p2,p3狀態都變成Fulfilled,p的狀態纔會變成Fulfilled,並將三個promise返回的結果,按參數的順序(而不是resolve()的順序)存入數組,傳給p的回調函數
var p1 = new Promise(function(resolve,reject){
        setTimeout(resolve,3000,'first')
    })

    var p2 = new Promise(function(resolve,reject){
        resolve('second')
    })

    var p3 = new Promise(function(resolve,reject){
        resolve(resolve,1000,'third')
    })

    Promise.all([p1,p2,p3]).then(function(values){
        console.log(values)
    })

約3秒後
[‘first’,‘second’,‘third’]

  • 當p1,p2,p3其中之一狀態變爲rejected,p的狀態也會變成rejected,並把第一個被reject的promise的返回值,傳給p的回調函數。
 var p1 = new Promise(function(resolve,reject){
        setTimeout(resolve,1000,'first')
    })

    var p2 = new Promise(function(resolve,reject){
        resolve(resolve,2000,'second')
    })

    var p3 = new Promise(function(resolve,reject){
        reject('third')
    })

    Promise.all([p1,p2,p3]).then(function(values){
        console.log(values)
    }).catch(function(error){
        console.log(error)
    })

輸出:
three

多個promise是同時開始執行的,並行執行的,而不是順序執行的。

function timePromisefy(delay){
        return new Promise(function(resolve){
            setTimeout(function(){
                resolve(delay);
            },delay)
        })
    }

    var startDate = Date.now()
    Promise.all([
        timePromisefy(1),
        timePromisefy(32),
        timePromisefy(64),
        timePromisefy(128),
        ]).then(function(values){
            console.log(Date.now() - startDate +'ms')
        })

最後輸出爲133ms //會大於128,但不是所有的時間總和

.resolve()

Promise.resolve(value)
Promise.resolve(promise)
Promise.resolve(thenable)

它可以看作是new Promise()的快捷方式

Promise.resolve('success')
//等同於
new Promise(function(resolve){
    resolve('success')
  })
         

.reject()

Promise.reject(reason)

它可以看作是new Promise()的快捷方式

Promise.resolve('success')
//等同於
new Promise(function(resolve,reject){
    reject(new Error('error'))
  })
         

4.總結

創建promise的流程:
1.使用new Promise(fn)或者它的快捷方式Promise.resolve()、Promise.reject(),返回一個promise對象
2.在fn中指定異步的處理
處理結果正常,調用resolve
處理結果錯誤,調用reject

最後希望大家能夠有所收穫!!!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章