異步的魅力:Promise

當下js最難以處理的的就是異步的任務(wù)會什么時候完成和完成之后的回調(diào)問題。
而回調(diào)函數(shù)也是最基礎(chǔ)最常用的處理js異步操作的辦法,而無限的回調(diào)會讓代碼的可維護性變得非常差。難以掌控的觸發(fā)狀態(tài),讓你自己寫的代碼當時還可以讀懂,但是過一段時間如果不重新理一下邏輯,估計自己都會忘了。借用一個例子:

  listen( "click", function handler(evt){
    setTimeout( function request(){
        ajax( "http://some.url.1", function response(text){
            if (text == "hello") {
                handler();
            }
            else if (text == "world") {
                request();
            }
        } );
    }, 1000) ;
} );
doSomething();

這種地獄回調(diào)會讓代碼變得多么不可控。

  • 第一步:執(zhí)行l(wèi)istern()
  • 第二步: doSomething()
  • 第三步:1000ms后執(zhí)行ajax()
  • 第四步:ajax完成后,根據(jù)結(jié)果執(zhí)行handler或request
    如果handler或request里面還有這樣的代碼,估計以后維護的同學來改個功能,心里非常的不爽。

在你不知道的javascript一書中,對于回調(diào)的信任問題做了闡述 當你使用第三方的庫的方法處理回調(diào)時很有可能遇到以下信任內(nèi)容

image.png

基于這些問題,Promise就橫空出世了。

一、什么是Promise

Promise是異步編程的一種解決方案,它有三種狀態(tài),分別是pending-進行中、resolved-已完成、rejected-已失敗

當Promise的狀態(tài)又pending轉(zhuǎn)變?yōu)閞esolved或rejected時,會執(zhí)行相應(yīng)的方法,并且狀態(tài)一旦改變,就無法再次改變狀態(tài),這也是它名字promise-承諾的由來

二、Promise的基本用法

function loadImg(src) {
  let promise = new Promise(function (resolve, reject) {
    // 新建一個img標簽
    let img = document.createElement('img')
    // 圖片加載成功
    img.onload = function () {
      resolve(img)
    }
    // 圖片加載失敗
    img.onerror = function () {
      reject('圖片加載失敗')
     }
    img.src = src
})
return promise
}
let src ='http://img5.imgtn.bdimg.com/it/u=415293130,2419074865&fm=27&gp=0.jpg'
let result = loadImg(src)
result.then((img) => {
  console.log(`加載成功,img寬度=${img.width}`)
}, (text) => {
  console.log(`error:${text}`)
})
  • result把圖片地址傳入loadImg中,loadImg返回一個Promise對象。
  • 根據(jù)圖片地址加載圖片,加載成功就執(zhí)行resolve,反之失敗就執(zhí)行reject。

then方法接收兩個函數(shù),一個是成功(狀態(tài)為resolved)的回調(diào)函數(shù),一個失?。顟B(tài)為rejected)的回調(diào)函數(shù)。

then方法的返回值不是一個promise對象就會被包裝成一個promise對象,所以then方法支持鏈式調(diào)用。
再來個按順序執(zhí)行的函數(shù):

function taskA() {
  console.log("Task A");
}
function taskB() {
  console.log("Task B");
}
function onRejected(error) {
  console.log("Catch Error: A or B", error);
}
function finalTask() {
  console.log("Final Task");
}
var promise = Promise.resolve();
promise
  .then(taskA)
  .then(taskB)
  .catch(onRejected)
  .then(finalTask);

這樣看著是不是非常順眼。

三、Promise.prototype.then() VS Promise.prototype.catch()

.then()方法使Promise原型鏈上的方法,它包含兩個參數(shù)方法,分別是已成功resolved的回調(diào)和已失敗rejected的回調(diào)

promise.then(
    () => { console.log('this is success callback') },
    () => { console.log('this is fail callback') }
)

.catch()的作用是捕獲Promise的錯誤,與then()的rejected回調(diào)作用幾乎一致。但是由于Promise的拋錯具有冒泡性質(zhì),能夠不斷傳遞,這樣就能夠在下一個catch()中統(tǒng)一處理這些錯誤。同時catch()也能夠捕獲then()中拋出的錯誤,所以建議不要使用then()的rejected回調(diào),而是統(tǒng)一使用catch()來處理錯誤

promise.then(
    () => { console.log('this is success callback') }
).catch(
    (err) => { console.log(err) }
)

同樣,catch()中也可以拋出錯誤,由于拋出的錯誤會在下一個catch中被捕獲處理,因此可以再添加catch()
使用rejects()方法改變狀態(tài)和拋出錯誤 throw new Error() 的作用是相同的

當狀態(tài)已經(jīng)改變?yōu)閞esolved后,即使拋出錯誤,也不會觸發(fā)then()的錯誤回調(diào)或者catch()方法

then() 和 catch() 都會返回一個新的Promise對象,可以鏈式調(diào)用

promise.then(
    () => { console.log('this is success callback') }
).catch(
    (err) => { console.log(err) }
).then(
    ...
).catch(
    ...
)

Promise實例的異步方法和then()中返回promise有什么區(qū)別?

// p1異步方法中返回p2
let p1 = new Promise ( (resolve, reject) => {
    resolve(p2)
} )
let p2 = new Promise ( ... )
// then()中返回promise
let p3 = new Promise ( (resolve, reject) => {
    resolve()
} )
let p4 = new Promise ( ... )
p3.then(
    () => return p4
)

p1異步方法中返回p2

p1的狀態(tài)取決于p2,如果p2為pending,p1將等待p2狀態(tài)的改變,p2的狀態(tài)一旦改變,p1將會立即執(zhí)行自己對應(yīng)的回調(diào),即then()中的方法針對的依然是p1

then()中返回promise

由于then()本身就會返回一個新的promise,所以后一個then()針對的永遠是一個新的promise,但是像上面代碼中我們自己手動返回p4,那么我們就可以在返回的promise中再次通過 resolve() 和 reject() 來改變狀態(tài)

Promise的其他api
Promise.resolve() / Promise.reject()
用來包裝一個現(xiàn)有對象,將其轉(zhuǎn)變?yōu)镻romise對象,但Promise.resolve()會根據(jù)參數(shù)情況返回不同的Promise:

參數(shù)是Promise:原樣返回
參數(shù)帶有then方法:轉(zhuǎn)換為Promise后立即執(zhí)行then方法
參數(shù)不帶then方法、不是對象或沒有參數(shù):返回resolved狀態(tài)的Promise

Promise.reject()會直接返回rejected狀態(tài)的Promise

Promise.all()
參數(shù)為Promise對象數(shù)組,如果有不是Promise的對象,將會先通過上面的Promise.resolve()方法轉(zhuǎn)換

var promise = Promise.all( [p1, p2, p3] )
promise.then(
    ...
).catch(
    ...
)

當p1、p2、p3的狀態(tài)都變成resolved時,promise才會變成resolved,并調(diào)用then()的已完成回調(diào),但只要有一個變成rejected狀態(tài),promise就會立刻變成rejected狀態(tài)

Promise.race()
var promise = Promise.race( [p1, p2, p3] )
promise.then(
    ...
).catch(
    ...
)

“競速”方法,參數(shù)與Promise.all()相同,不同的是,參數(shù)中的p1、p2、p3只要有一個改變狀態(tài),promise就會立刻變成相同的狀態(tài)并執(zhí)行對于的回調(diào)

Promise.done() / Promise. finally()
Promise.done() 的用法類似 .then() ,可以提供resolved和rejected方法,也可以不提供任何參數(shù),它的主要作用是在回調(diào)鏈的尾端捕捉前面沒有被 .catch() 捕捉到的錯誤

Promise. finally() 接受一個方法作為參數(shù),這個方法不管promise最終的狀態(tài)是怎樣,都一定會被執(zhí)行

四、大神實現(xiàn)的Promise源碼

下面是一個網(wǎng)上找的實現(xiàn)Promise的代碼。

const PENDING = 0;
const FULFILLED = 1;
const REJECTED = 2;
class PromiseDemo {
    constructor(callback) {
        this.status = PENDING;
        this.value = null;
        this.defferd = [];
        setTimeout(
            callback.bind(this, this.resolve.bind(this), this.reject.bind(this)
        ), 10);
    };
    resolve(result) {
        this.status = FULFILLED;
        this.value = result;
        this.done();
    }
    reject(error) {
        this.status = REJECTED;
        this.value = error;
    }
    handle(fn) {
        if (!fn) {
            return;
        }
        var value = this.value;
        var t = this.status;
        var p;
        if (t == PENDING) {
            this.defferd.push(fn);
        } else {
            if (t == FULFILLED && typeof fn.onfulfiled == 'function') {
                p = fn.onfulfiled(value);
            }
            if (t == REJECTED && typeof fn.onrejected == 'function') {
                p = fn.onrejected(value);
            }
            var promise = fn.promise;
            if (promise) {
                if (p && p.constructor == this) {
                    p.defferd = promise.defferd;
                } else {
                    p = this;
                    p.defferd = promise.defferd;
                    this.done();
                }
            }
        }
    }
    done() {
        if (this.status == PENDING) {
            return;
        }
        var defferd = this.defferd;
        for (var i = 0; i < defferd.length; i++) {
            this.handle(defferd[i]);
        }
    }
    then(success, fail) {
        var o = {
            onfulfiled: success,
            onrejected: fail
        };
        var status = this.status;
        o.promise = new this.constructor(function () { });
        if (status == PENDING) {
            this.defferd.push(o);
        } else if (status == FULFILLED || status == REJECTED) {
            this.handle(o);
        }
        return o.promise;
    }
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容