溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

Promise的原理和基礎(chǔ)用法介紹

發(fā)布時(shí)間:2021-09-13 09:37:22 來源:億速云 閱讀:191 作者:chen 欄目:web開發(fā)

這篇文章主要介紹“Promise的原理和基礎(chǔ)用法介紹”,在日常操作中,相信很多人在Promise的原理和基礎(chǔ)用法介紹問題上存在疑惑,小編查閱了各式資料,整理出簡(jiǎn)單好用的操作方法,希望對(duì)大家解答”Promise的原理和基礎(chǔ)用法介紹”的疑惑有所幫助!接下來,請(qǐng)跟著小編一起來學(xué)習(xí)吧!

一、Promise基礎(chǔ)用法

1.1 基本用法

new Promise(function(resolve, reject) {
    //待處理的異步邏輯
    //處理結(jié)束后,調(diào)用resolve或reject方法
})
  • 新建一個(gè)promise很簡(jiǎn)單,只需要new一個(gè)promise對(duì)象即可。所以promise本質(zhì)上就是一個(gè)函數(shù),它接受一個(gè)函數(shù)作為參數(shù),并且會(huì)返回promise對(duì)象,這就給鏈?zhǔn)秸{(diào)用提供了基礎(chǔ)

  • 其實(shí)Promise函數(shù)的使命,就是構(gòu)建出它的實(shí)例,并且負(fù)責(zé)幫我們管理這些實(shí)例。而這些實(shí)例有以下三種狀態(tài):

  • pending: 初始狀態(tài),位履行或拒絕

  • fulfilled: 意味著操作成功完成

  • rejected: 意味著操作失敗

pending 狀態(tài)的 Promise對(duì)象可能以 fulfilled狀態(tài)返回了一個(gè)值,也可能被某種理由(異常信息)拒絕(reject)了。當(dāng)其中任一種情況出現(xiàn)時(shí),Promise 對(duì)象的 then 方法綁定的處理方法(handlers)就會(huì)被調(diào)用,then方法分別指定了resolve方法和reject方法的回調(diào)函數(shù)

Promise的原理和基礎(chǔ)用法介紹

var promise = 
new Promise(function(resolve, reject) {
  if (/* 異步操作成功 */){
    resolve(value);
  } 
else {
    reject(error);
  }
});
promise.then(function(value) {
  // 如果調(diào)用了resolve方法,執(zhí)行此函數(shù)
}, 
function(value) {
  // 如果調(diào)用了reject方法,執(zhí)行此函數(shù)
});

上述代碼很清晰的展示了promise對(duì)象運(yùn)行的機(jī)制。下面再看一個(gè)示例:

var getJSON = 
function(url) {
  var promise = 
new Promise(function(resolve, reject){
    var client = 
new XMLHttpRequest();
    client.open("GET", url);
    client.>
    client.responseType = 
"json";
    client.setRequestHeader("Accept", 
"application/json");
    client.send();
    function handler(
) {
      if (this.status === 
200) { 
              resolve(this.response); 
          } 
else { 
              reject(new Error(this.statusText)); 
          }
    };
  });
  return promise;
};
getJSON("/posts.json").then(function(json) {
  console.log('Contents: ' + json);
}, 
function(error) {
  console.error('出錯(cuò)了', error);
});

上面代碼中,resolve方法和reject方法調(diào)用時(shí),都帶有參數(shù)。它們的參數(shù)會(huì)被傳遞給回調(diào)函數(shù)。reject方法的參數(shù)通常是Error對(duì)象的實(shí)例,而resolve方法的參數(shù)除了正常的值以外,還可能是另一個(gè)Promise實(shí)例,比如像下面這樣。

var p1 = 
new Promise(function(resolve, reject){
  // ... some code
});
var p2 = 
new Promise(function(resolve, reject){
  // ... some code
  resolve(p1);
})

上面代碼中,p1p2都是Promise的實(shí)例,但是p2resolve方法將p1作為參數(shù),這時(shí)p1的狀態(tài)就會(huì)傳遞給p2。如果調(diào)用的時(shí)候,p1的狀態(tài)是pending,那么p2的回調(diào)函數(shù)就會(huì)等待p1的狀態(tài)改變;如果p1的狀態(tài)已經(jīng)是fulfilled或者rejected,那么p2的回調(diào)函數(shù)將會(huì)立刻執(zhí)行

1.2 promise捕獲錯(cuò)誤

Promise.prototype.catch方法是Promise.prototype.then(null, rejection)的別名,用于指定發(fā)生錯(cuò)誤時(shí)的回調(diào)函數(shù)

getJSON("/visa.json").then(function(result) {
  // some code
}).catch(function(error) {
  // 處理前一個(gè)回調(diào)函數(shù)運(yùn)行時(shí)發(fā)生的錯(cuò)誤
  console.log('出錯(cuò)啦!', error);
});

Promise對(duì)象的錯(cuò)誤具有“冒泡”性質(zhì),會(huì)一直向后傳遞,直到被捕獲為止。也就是說,錯(cuò)誤總是會(huì)被下一個(gè)catch語句捕獲

getJSON("/visa.json").then(function(json) {
  return json.name;
}).then(function(name) {
  // proceed
}).catch(function(error) {
    //處理前面任一個(gè)then函數(shù)拋出的錯(cuò)誤
});

1.3 常用的promise方法

Promise.all方法

Promise.all方法用于將多個(gè)Promise實(shí)例,包裝成一個(gè)新的Promise實(shí)例

var p = 
Promise.all([p1,p2,p3]);
  • 上面代碼中,Promise.all方法接受一個(gè)數(shù)組作為參數(shù),p1p2、p3都是Promise對(duì)象的實(shí)例。(Promise.all方法的參數(shù)不一定是數(shù)組,但是必須具有iterator接口,且返回的每個(gè)成員都是Promise實(shí)例。)

p的狀態(tài)由p1p2、p3決定,分成兩種情況

  • 只有p1、p2p3的狀態(tài)都變成fulfilled,p的狀態(tài)才會(huì)變成fulfilled,此時(shí)p1、p2、p3的返回值組成一個(gè)數(shù)組,傳遞給p的回調(diào)函數(shù)

  • 只要p1、p2、p3之中有一個(gè)被rejected,p的狀態(tài)就變成rejected,此時(shí)第一個(gè)被reject的實(shí)例的返回值,會(huì)傳遞給p的回調(diào)函數(shù)

// 生成一個(gè)Promise對(duì)象的數(shù)組
var promises = [2, 
3, 
5, 
7, 
11, 
13].map(function(id){
  return getJSON("/get/addr" + id + 
".json");
});
Promise.all(promises).then(function(posts) {
  // ...  
}).catch(function(reason){
  // ...
});

Promise.race方法

Promise.race方法同樣是將多個(gè)Promise實(shí)例,包裝成一個(gè)新的Promise實(shí)例。

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

上面代碼中,只要p1、p2p3之中有一個(gè)實(shí)例率先改變狀態(tài),p的狀態(tài)就跟著改變。那個(gè)率先改變的Promise實(shí)例的返回值,就傳遞給p的返回值

  • 如果Promise.all方法和Promise.race方法的參數(shù),不是Promise實(shí)例,就會(huì)先調(diào)用下面講到的Promise.resolve方法,將參數(shù)轉(zhuǎn)為Promise實(shí)例,再進(jìn)一步處理

Promise.resolve

有時(shí)需要將現(xiàn)有對(duì)象轉(zhuǎn)為Promise對(duì)象,Promise.resolve方法就起到這個(gè)作用

var jsPromise = 
Promise.resolve($.ajax('/whatever.json'));

上面代碼將jQuery生成deferred對(duì)象,轉(zhuǎn)為一個(gè)新的ES6Promise對(duì)象

  • 如果Promise.resolve方法的參數(shù),不是具有then方法的對(duì)象(又稱thenable對(duì)象),則返回一個(gè)新的Promise對(duì)象,且它的狀態(tài)為fulfilled。

var p = 
Promise.resolve('Hello');
p.then(function (s){
  console.log(s)
});
// Hello
  • 上面代碼生成一個(gè)新的Promise對(duì)象的實(shí)例p,它的狀態(tài)為fulfilled,所以回調(diào)函數(shù)會(huì)立即執(zhí)行,Promise.resolve方法的參數(shù)就是回調(diào)函數(shù)的參數(shù)

  • 如果Promise.resolve方法的參數(shù)是一個(gè)Promise對(duì)象的實(shí)例,則會(huì)被原封不動(dòng)地返回

  • Promise.reject(reason)方法也會(huì)返回一個(gè)新的Promise實(shí)例,該實(shí)例的狀態(tài)為rejected。Promise.reject方法的參數(shù)reason,會(huì)被傳遞給實(shí)例的回調(diào)函數(shù)

var p = 
Promise.reject('出錯(cuò)啦');
p.then(null, 
function (error){
  console.log(error)
});
// 出錯(cuò)了

1.4 Async/await簡(jiǎn)化寫法

function getDataAsync (url) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            var res = {
                url: url,
                data: 
Math.random()
            }
            resolve(res)
        }, 
1000)
    })
}
async function getData (
) {
    var res1 = 
await getDataAsync('/page/1?param=123')
    console.log(res1)
    var res2 = 
await getDataAsync(`/page/2?param=${res1.data}`)
    console.log(res2)
    var res3 = 
await getDataAsync(`/page/2?param=${res2.data}`)
    console.log(res3)
}

async/await 是基于 Promise 的,因?yàn)槭褂?nbsp;async 修飾的方法最終返回一個(gè) Promise, 實(shí)際上,async/await 可以看做是使用 Generator 函數(shù)處理異步的語法糖,我們來看看如何使用 Generator 函數(shù)處理異步

1.5 Generator

首先異步函數(shù)依然是:

function getDataAsync (url) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            var res = {
                url: url,
                data: 
Math.random()
            }
            resolve(res)
        }, 
1000)
    })
}

使用 Generator 函數(shù)可以這樣寫

function * 
getData (
) {
    var res1 = 
yield getDataAsync('/page/1?param=123')
    console.log(res1)
    var res2 = 
yield getDataAsync(`/page/2?param=${res1.data}`)
    console.log(res2)
    var res3 = 
yield getDataAsync(`/page/2?param=${res2.data}`)
    console.log(res3))
}

然后我們這樣逐步執(zhí)行

var g = getData()
g.next().value.then(res1 => {
    g.next(res1).value.then(res2 => {
        g.next(res2).value.then(() => {
            g.next()
        })
    })
})

上面的代碼,我們逐步調(diào)用遍歷器的 next() 方法,由于每一個(gè) next() 方法返回值的 value 屬性為一個(gè) Promise 對(duì)象,所以我們?yōu)槠涮砑?nbsp;then 方法, 在 then方法里面接著運(yùn)行 next 方法挪移遍歷器指針,直到 Generator 函數(shù)運(yùn)行完成,實(shí)際上,這個(gè)過程我們不必手動(dòng)完成,可以封裝成一個(gè)簡(jiǎn)單的執(zhí)行器

function run (gen) {
    var g = gen()

    function next (data) {
        var res = g.next(data)
        if (res.done) 
return res.value
        res.value.then((data) => {
            next(data)
        })
    }

    next()

}

run方法用來自動(dòng)運(yùn)行異步的 Generator 函數(shù),其實(shí)就是一個(gè)遞歸的過程調(diào)用的過程。這樣我們就不必手動(dòng)執(zhí)行 Generator 函數(shù)了。 有了 run 方法,我們只需要這樣運(yùn)行 getData 方法

run(getData)

這樣,我們就可以把異步操作封裝到 Generator 函數(shù)內(nèi)部,使用 run 方法作為 Generator 函數(shù)的自執(zhí)行器,來處理異步。其實(shí)我們不難發(fā)現(xiàn), async/await 方法相比于 Generator 處理異步的方式,有很多相似的地方,只不過 async/await 在語義化方面更加明顯,同時(shí) async/await 不需要我們手寫執(zhí)行器,其內(nèi)部已經(jīng)幫我們封裝好了,這就是為什么說 async/await 是 Generator 函數(shù)處理異步的語法糖了

二、Promise實(shí)現(xiàn)原理剖析

2.1 Promise標(biāo)準(zhǔn)

  • Promise 規(guī)范有很多,如Promise/A,Promise/B,Promise/D以及 Promise/A 的升級(jí)版 Promise/A+。ES6中采用了 Promise/A+ 規(guī)范

中文版規(guī)范:  Promises/A+規(guī)范(中文)

Promise標(biāo)準(zhǔn)解讀

  • 一個(gè)promise的當(dāng)前狀態(tài)只能是pending、fulfilledrejected三種之一。狀態(tài)改變只能是pendingfulfilled或者pendingrejected。狀態(tài)改變不可逆

  • promisethen方法接收兩個(gè)可選參數(shù),表示該promise狀態(tài)改變時(shí)的回調(diào)(promise.then(onFulfilled, onRejected))。then方法返回一個(gè)promise。then 方法可以被同一個(gè) promise 調(diào)用多次

2.2 實(shí)現(xiàn)Promise

構(gòu)造函數(shù)

function Promise(resolver) {}

原型方法

Promise.prototype.then = 
function(
) {}
Promise.prototype.catch = 
function(
) {}

靜態(tài)方法

Promise.resolve = 
function(
) {}
Promise.reject = 
function(
) {}
Promise.all = 
function(
) {}
Promise.race = 
function(
) {}

2.3 極簡(jiǎn)promise雛形

function Promise(fn) {
    var value = 
null,
        callbacks = [];  //callbacks為數(shù)組,因?yàn)榭赡芡瑫r(shí)有很多個(gè)回調(diào)
    this.then = 
function (onFulfilled) {
        callbacks.push(onFulfilled);
    };
    function resolve(value) {
        callbacks.forEach(function (callback) {
            callback(value);
        });
    }
    fn(resolve);
}

大致的邏輯是這樣的

  • 調(diào)用then方法,將想要在Promise異步操作成功時(shí)執(zhí)行的回調(diào)放入callbacks隊(duì)列,其實(shí)也就是注冊(cè)回調(diào)函數(shù),可以向觀察者模式方向思考

  • 創(chuàng)建Promise實(shí)例時(shí)傳入的函數(shù)會(huì)被賦予一個(gè)函數(shù)類型的參數(shù),即resolve,它接收一個(gè)參數(shù)value,代表異步操作返回的結(jié)果,當(dāng)一步操作執(zhí)行成功后,用戶會(huì)調(diào)用resolve方法,這時(shí)候其實(shí)真正執(zhí)行的操作是將callbacks隊(duì)列中的回調(diào)一一執(zhí)行

//例1
function getUserId(
) {
    return new Promise(function(resolve) {
        //異步請(qǐng)求
        http.get(url, 
function(results) {
            resolve(results.id)
        })
    })
}
getUserId().then(function(id) {
    //一些處理
})
// 結(jié)合例子1分析

// fn 就是getUserId函數(shù)
function Promise(fn) {
    var value = 
null,
        callbacks = [];  //callbacks為數(shù)組,因?yàn)榭赡芡瑫r(shí)有很多個(gè)回調(diào)
    
    // 當(dāng)用戶調(diào)用getUserId().then的時(shí)候開始注冊(cè)傳進(jìn)來的回調(diào)函數(shù)
    // onFulfilled就是例子中的function(id){}
    // 把then的回調(diào)函數(shù)收集起來 在resolve的時(shí)候調(diào)用
    this.then = 
function (onFulfilled) {
        callbacks.push(onFulfilled);
    };
    
    // value是fn函數(shù)執(zhí)行后返回的值
    function resolve(value) {
        // callbacks是傳給then的回調(diào)函數(shù)就是例子中的function(id){}
        // 遍歷用戶通過then傳遞進(jìn)來的回調(diào)函數(shù)把resolve成功的結(jié)果返回給then調(diào)用即then(function(data){ console.log(data) }) 這里的data就是通過這里調(diào)用返回
        callbacks.forEach(function (callback) {
            callback(value);
        });
    }
    
    //執(zhí)行fn函數(shù)即getUserId()并且傳入函數(shù)參數(shù)resolve 當(dāng)fn執(zhí)行完成返回的值傳遞給resolve函數(shù)
    fn(resolve);
}

結(jié)合例1中的代碼來看,首先new Promise時(shí),傳給promise的函數(shù)發(fā)送異步請(qǐng)求,接著調(diào)用promise對(duì)象的then屬性,注冊(cè)請(qǐng)求成功的回調(diào)函數(shù),然后當(dāng)異步請(qǐng)求發(fā)送成功時(shí),調(diào)用resolve(results.id)方法, 該方法執(zhí)行then方法注冊(cè)的回調(diào)數(shù)組

  • then方法應(yīng)該能夠鏈?zhǔn)秸{(diào)用,但是上面的最基礎(chǔ)簡(jiǎn)單的版本顯然無法支持鏈?zhǔn)秸{(diào)用。想讓then方法支持鏈?zhǔn)秸{(diào)用,其實(shí)也是很簡(jiǎn)單的

this.then = 
function (onFulfilled) {
    callbacks.push(onFulfilled);
    return this;
};

只要簡(jiǎn)單一句話就可以實(shí)現(xiàn)類似下面的鏈?zhǔn)秸{(diào)用

// 例2
getUserId().then(function (id) {
    // 一些處理
}).then(function (id) {
    // 一些處理
});

2.4 加入延時(shí)機(jī)制

上述代碼可能還存在一個(gè)問題:如果在then方法注冊(cè)回調(diào)之前,resolve函數(shù)就執(zhí)行了,怎么辦?比如promise內(nèi)部的函數(shù)是同步函數(shù)

// 例3
function getUserId(
) {
    return new Promise(function (resolve) {
        resolve(9876);
    });
}
getUserId().then(function (id) {
    // 一些處理
});

這顯然是不允許的,Promises/A+規(guī)范明確要求回調(diào)需要通過異步方式執(zhí)行,用以保證一致可靠的執(zhí)行順序。因此我們要加入一些處理,保證在resolve執(zhí)行之前,then方法已經(jīng)注冊(cè)完所有的回調(diào)。我們可以這樣改造下resolve函數(shù):

function resolve(value) {
    setTimeout(function(
) {
        callbacks.forEach(function (callback) {
            callback(value);
        });
    }, 
0)
}

上述代碼的思路也很簡(jiǎn)單,就是通過setTimeout機(jī)制,將resolve中執(zhí)行回調(diào)的邏輯放置到JS任務(wù)隊(duì)列末尾,以保證在resolve執(zhí)行時(shí),then方法的回調(diào)函數(shù)已經(jīng)注冊(cè)完成

  • 但是,這樣好像還存在一個(gè)問題,可以細(xì)想一下:如果Promise異步操作已經(jīng)成功,這時(shí),在異步操作成功之前注冊(cè)的回調(diào)都會(huì)執(zhí)行,但是在Promise異步操作成功這之后調(diào)用的then注冊(cè)的回調(diào)就再也不會(huì)執(zhí)行了,這顯然不是我們想要的

2.5 加入狀態(tài)

我們必須加入狀態(tài)機(jī)制,也就是大家熟知的pending、fulfilled、rejected

Promises/A+規(guī)范中的2.1 Promise States中明確規(guī)定了,pending可以轉(zhuǎn)化為fulfilledrejected并且只能轉(zhuǎn)化一次,也就是說如果pending轉(zhuǎn)化到fulfilled狀態(tài),那么就不能再轉(zhuǎn)化到rejected。并且fulfilledrejected狀態(tài)只能由pending轉(zhuǎn)化而來,兩者之間不能互相轉(zhuǎn)換

Promise的原理和基礎(chǔ)用法介紹

//改進(jìn)后的代碼是這樣的:

function Promise(fn) {
    var state = 
'pending',
        value = 
null,
        callbacks = [];
    this.then = 
function (onFulfilled) {
        if (state === 
'pending') {
            callbacks.push(onFulfilled);
            return this;
        }
        onFulfilled(value);
        return this;
    };
    function resolve(newValue) {
        value = newValue;
        state = 
'fulfilled';
        setTimeout(function (
) {
            callbacks.forEach(function (callback) {
                callback(value);
            });
        }, 
0);
    }
    fn(resolve);
}

上述代碼的思路是這樣的:resolve執(zhí)行時(shí),會(huì)將狀態(tài)設(shè)置為fulfilled,在此之后調(diào)用then添加的新回調(diào),都會(huì)立即執(zhí)行

2.6 鏈?zhǔn)絇romise

如果用戶在then函數(shù)里面注冊(cè)的仍然是一個(gè)Promise,該如何解決?比如下面的例4

// 例4
getUserId()
    .then(getUserJobById)
    .then(function (job) {
        // 對(duì)job的處理
    });
function getUserJobById(id) {
    return new Promise(function (resolve) {
        http.get(baseUrl + id, 
function(job) {
            resolve(job);
        });
    });
}
  • 這種場(chǎng)景相信用過promise的人都知道會(huì)有很多,那么類似這種就是所謂的鏈?zhǔn)?code>Promise

  • 鏈?zhǔn)?code>Promise是指在當(dāng)前promise達(dá)到fulfilled狀態(tài)后,即開始進(jìn)行下一個(gè)promise(后鄰promise)。那么我們?nèi)绾毋暯赢?dāng)前promise和后鄰promise呢?(這是這里的難點(diǎn)

  • 只要在then方法里面return一個(gè)promise就好啦。Promises/A+規(guī)范中的2.2.7就是這樣

下面來看看這段暗藏玄機(jī)的then方法和resolve方法改造代碼

function Promise(fn) {
    var state = 
'pending',
        value = 
null,
        callbacks = [];
    this.then = 
function (onFulfilled) {
        return new Promise(function (resolve) {
            handle({
                onFulfilled: onFulfilled || 
null,
                resolve: resolve
            });
        });
    };
    function handle(callback) {
        if (state === 
'pending') {
            callbacks.push(callback);
            return;
        }
        //如果then中沒有傳遞任何東西
        if(!callback.onFulfilled) {
            callback.resolve(value);
            return;
        }
        var ret = callback.onFulfilled(value);
        callback.resolve(ret);
    }
    
    function resolve(newValue) {
        if (newValue && (typeof newValue === 
'object' || 
typeof newValue === 
'function')) {
            var then = newValue.then;
            if (typeof then === 
'function') {
                then.call(newValue, resolve);
                return;
            }
        }
        state = 
'fulfilled';
        value = newValue;
        setTimeout(function (
) {
            callbacks.forEach(function (callback) {
                handle(callback);
            });
        }, 
0);
    }
    fn(resolve);
}

我們結(jié)合例4的代碼,分析下上面的代碼邏輯,為了方便閱讀,我把例4的代碼貼在這里

// 例4
getUserId()
    .then(getUserJobById)
    .then(function (job) {
        // 對(duì)job的處理
    });
function getUserJobById(id) {
    return new Promise(function (resolve) {
        http.get(baseUrl + id, 
function(job) {
            resolve(job);
        });
    });
}
  • then方法中,創(chuàng)建并返回了新的Promise實(shí)例,這是串行Promise的基礎(chǔ),并且支持鏈?zhǔn)秸{(diào)用

  • handle方法是promise內(nèi)部的方法。then方法傳入的形參onFulfilled以及創(chuàng)建新Promise實(shí)例時(shí)傳入的resolve均被push到當(dāng)前promisecallbacks隊(duì)列中,這是銜接當(dāng)前promise和后鄰promise的關(guān)鍵所在

  • getUserId生成的promise(簡(jiǎn)稱getUserId promise)異步操作成功,執(zhí)行其內(nèi)部方法resolve,傳入的參數(shù)正是異步操作的結(jié)果id

  • 調(diào)用handle方法處理callbacks隊(duì)列中的回調(diào):getUserJobById方法,生成新的promise(getUserJobById promise

  • 執(zhí)行之前由getUserId promisethen方法生成的新promise(稱為bridge promise)的resolve方法,傳入?yún)?shù)為getUserJobById promise。這種情況下,會(huì)將該resolve方法傳入getUserJobById promisethen方法中,并直接返回

  • getUserJobById promise異步操作成功時(shí),執(zhí)行其callbacks中的回調(diào):getUserId bridge promise中的resolve方法

  • 最后執(zhí)行getUserId bridge promise的后鄰promisecallbacks中的回調(diào)

2.7 失敗處理

在異步操作失敗時(shí),標(biāo)記其狀態(tài)為rejected,并執(zhí)行注冊(cè)的失敗回調(diào)

//例5
function getUserId(
) {
    return new Promise(function(resolve) {
        //異步請(qǐng)求
        http.get(url, 
function(error, results) {
            if (error) {
                reject(error);
            }
            resolve(results.id)
        })
    })
}
getUserId().then(function(id) {
    //一些處理
}, 
function(error) {
    console.log(error)
})

有了之前處理fulfilled狀態(tài)的經(jīng)驗(yàn),支持錯(cuò)誤處理變得很容易,只需要在注冊(cè)回調(diào)、處理狀態(tài)變更上都要加入新的邏輯

function Promise(fn) {
    var state = 
'pending',
        value = 
null,
        callbacks = [];
    this.then = 
function (onFulfilled, onRejected) {
        return new Promise(function (resolve, reject) {
            handle({
                onFulfilled: onFulfilled || 
null,
                onRejected: onRejected || 
null,
                resolve: resolve,
                reject: reject
            });
        });
    };
    function handle(callback) {
        if (state === 
'pending') {
            callbacks.push(callback);
            return;
        }
        var cb = state === 
'fulfilled' ? callback.onFulfilled : callback.onRejected,
            ret;
        if (cb === 
null) {
            cb = state === 
'fulfilled' ? callback.resolve : callback.reject;
            cb(value);
            return;
        }
        ret = cb(value);
        callback.resolve(ret);
    }
    function resolve(newValue) {
        if (newValue && (typeof newValue === 
'object' || 
typeof newValue === 
'function')) {
            var then = newValue.then;
            if (typeof then === 
'function') {
                then.call(newValue, resolve, reject);
                return;
            }
        }
        state = 
'fulfilled';
        value = newValue;
        execute();
    }
    function reject(reason) {
        state = 
'rejected';
        value = reason;
        execute();
    }
    function execute(
) {
        setTimeout(function (
) {
            callbacks.forEach(function (callback) {
                handle(callback);
            });
        }, 
0);
    }
    fn(resolve, reject);
}

上述代碼增加了新的reject方法,供異步操作失敗時(shí)調(diào)用,同時(shí)抽出了resolvereject共用的部分,形成execute方法

錯(cuò)誤冒泡是上述代碼已經(jīng)支持,且非常實(shí)用的一個(gè)特性。在handle中發(fā)現(xiàn)沒有指定異步操作失敗的回調(diào)時(shí),會(huì)直接將bridge promise(then函數(shù)返回的promise,后同)設(shè)為rejected狀態(tài),如此達(dá)成執(zhí)行后續(xù)失敗回調(diào)的效果。這有利于簡(jiǎn)化串行Promise的失敗處理成本,因?yàn)橐唤M異步操作往往會(huì)對(duì)應(yīng)一個(gè)實(shí)際功能,失敗處理方法通常是一致的

//例6
getUserId()
    .then(getUserJobById)
    .then(function (job) {
        // 處理job
    }, 
function (error) {
        // getUserId或者getUerJobById時(shí)出現(xiàn)的錯(cuò)誤
        console.log(error);
    });

2.8 異常處理

如果在執(zhí)行成功回調(diào)、失敗回調(diào)時(shí)代碼出錯(cuò)怎么辦?對(duì)于這類異常,可以使用try-catch捕獲錯(cuò)誤,并將bridge promise設(shè)為rejected狀態(tài)。handle方法改造如下

function handle(callback) {
    if (state === 
'pending') {
        callbacks.push(callback);
        return;
    }
    var cb = state === 
'fulfilled' ? callback.onFulfilled : callback.onRejected,
        ret;
    if (cb === 
null) {
        cb = state === 
'fulfilled' ? callback.resolve : callback.reject;
        cb(value);
        return;
    }
    try {
        ret = cb(value);
        callback.resolve(ret);
    } 
catch (e) {
        callback.reject(e);
    } 
}

如果在異步操作中,多次執(zhí)行resolve或者reject會(huì)重復(fù)處理后續(xù)回調(diào),可以通過內(nèi)置一個(gè)標(biāo)志位解決

2.9 完整實(shí)現(xiàn)

// 三種狀態(tài)
const PENDING = 
"pending";
const RESOLVED = 
"resolved";
const REJECTED = 
"rejected";
// promise 接收一個(gè)函數(shù)參數(shù),該函數(shù)會(huì)立即執(zhí)行
function MyPromise(fn) {
  let _this = 
this;
  _this.currentState = PENDING;
  _this.value = 
undefined;
  // 用于保存 then 中的回調(diào),只有當(dāng) promise
  // 狀態(tài)為 pending 時(shí)才會(huì)緩存,并且每個(gè)實(shí)例至多緩存一個(gè)
  _this.resolvedCallbacks = [];
  _this.rejectedCallbacks = [];

  _this.resolve = 
function (value) {
    if (value 
instanceof MyPromise) {
      // 如果 value 是個(gè) Promise,遞歸執(zhí)行
      return value.then(_this.resolve, _this.reject)
    }
    setTimeout(() => { 
// 異步執(zhí)行,保證執(zhí)行順序
      if (_this.currentState === PENDING) {
        _this.currentState = RESOLVED;
        _this.value = value;
        _this.resolvedCallbacks.forEach(cb => cb());
      }
    })
  };

  _this.reject = 
function (reason) {
    setTimeout(() => { 
// 異步執(zhí)行,保證執(zhí)行順序
      if (_this.currentState === PENDING) {
        _this.currentState = REJECTED;
        _this.value = reason;
        _this.rejectedCallbacks.forEach(cb => cb());
      }
    })
  }
  // 用于解決以下問題
  // new Promise(() => throw Error('error))
  try {
    fn(_this.resolve, _this.reject);
  } 
catch (e) {
    _this.reject(e);
  }
}

MyPromise.prototype.then = 
function (onResolved, onRejected) {
  var self = 
this;
  // 規(guī)范 2.2.7,then 必須返回一個(gè)新的 promise
  var promise2;
  // 規(guī)范 2.2.onResolved 和 onRejected 都為可選參數(shù)
  // 如果類型不是函數(shù)需要忽略,同時(shí)也實(shí)現(xiàn)了透?jìng)?
  // Promise.resolve(4).then().then((value) => console.log(value))
  typeof 'function' ? onResolved : 
v => v;
  typeof 'function' ? onRejected : 
r => throw r;

  if (self.currentState === RESOLVED) {
    return (promise2 = 
new MyPromise(function (resolve, reject) {
      // 規(guī)范 2.2.4,保證 onFulfilled,onRjected 異步執(zhí)行
      // 所以用了 setTimeout 包裹下
      setTimeout(function (
) {
        try {
          var x = onResolved(self.value);
          resolutionProcedure(promise2, x, resolve, reject);
        } 
catch (reason) {
          reject(reason);
        }
      });
    }));
  }

  if (self.currentState === REJECTED) {
    return (promise2 = 
new MyPromise(function (resolve, reject) {
      setTimeout(function (
) {
        // 異步執(zhí)行onRejected
        try {
          var x = onRejected(self.value);
          resolutionProcedure(promise2, x, resolve, reject);
        } 
catch (reason) {
          reject(reason);
        }
      });
    }));
  }

  if (self.currentState === PENDING) {
    return (promise2 = 
new MyPromise(function (resolve, reject) {
      self.resolvedCallbacks.push(function (
) {
        // 考慮到可能會(huì)有報(bào)錯(cuò),所以使用 try/catch 包裹
        try {
          var x = onResolved(self.value);
          resolutionProcedure(promise2, x, resolve, reject);
        } 
catch (r) {
          reject(r);
        }
      });

      self.rejectedCallbacks.push(function (
) {
        try {
          var x = onRejected(self.value);
          resolutionProcedure(promise2, x, resolve, reject);
        } 
catch (r) {
          reject(r);
        }
      });
    }));
  }
};
// 規(guī)范 2.3
function resolutionProcedure(promise2, x, resolve, reject) {
  // 規(guī)范 2.3.1,x 不能和 promise2 相同,避免循環(huán)引用
  if (promise2 === x) {
    return reject(new TypeError("Error"));
  }
  // 規(guī)范 2.3.2
  // 如果 x 為 Promise,狀態(tài)為 pending 需要繼續(xù)等待否則執(zhí)行
  if (x 
instanceof MyPromise) {
    if (x.currentState === PENDING) {
      x.then(function (value) {
        // 再次調(diào)用該函數(shù)是為了確認(rèn) x resolve 的
        // 參數(shù)是什么類型,如果是基本類型就再次 resolve
        // 把值傳給下個(gè) then
        resolutionProcedure(promise2, value, resolve, reject);
      }, reject);
    } 
else {
      x.then(resolve, reject);
    }
    return;
  }
  // 規(guī)范 2.3.3.3.3
  // reject 或者 resolve 其中一個(gè)執(zhí)行過得話,忽略其他的
  let called = 
false;
  // 規(guī)范 2.3.3,判斷 x 是否為對(duì)象或者函數(shù)
  if (x !== 
null && (typeof x === 
"object" || 
typeof x === 
"function")) {
    // 規(guī)范 2.3.3.2,如果不能取出 then,就 reject
    try {
      // 規(guī)范 2.3.3.1
      let then = x.then;
      // 如果 then 是函數(shù),調(diào)用 x.then
      if (typeof then === 
"function") {
        // 規(guī)范 2.3.3.3
        then.call(
          x,
          y => {
            if (called) 
return;
            called = 
true;
            // 規(guī)范 2.3.3.3.1
            resolutionProcedure(promise2, y, resolve, reject);
          },
          e => {
            if (called) 
return;
            called = 
true;
            reject(e);
          }
        );
      } 
else {
        // 規(guī)范 2.3.3.4
        resolve(x);
      }
    } 
catch (e) {
      if (called) 
return;
      called = 
true;
      reject(e);
    }
  } 
else {
    // 規(guī)范 2.3.4,x 為基本類型
    resolve(x);
  }
}

2.10 小結(jié)

這里一定要注意的點(diǎn)是promise里面的then函數(shù)僅僅是注冊(cè)了后續(xù)需要執(zhí)行的代碼,真正的執(zhí)行是在resolve方法里面執(zhí)行的,理清了這層,再來分析源碼會(huì)省力的多

現(xiàn)在回顧下Promise的實(shí)現(xiàn)過程,其主要使用了設(shè)計(jì)模式中的觀察者模式

  • 通過Promise.prototype.thenPromise.prototype.catch方法將觀察者方法注冊(cè)到被觀察者Promise對(duì)象中,同時(shí)返回一個(gè)新的Promise對(duì)象,以便可以鏈?zhǔn)秸{(diào)用

  • 被觀察者管理內(nèi)部pendingfulfilledrejected的狀態(tài)轉(zhuǎn)變,同時(shí)通過構(gòu)造函數(shù)中傳遞的resolvereject方法以主動(dòng)觸發(fā)狀態(tài)轉(zhuǎn)變和通知觀察者

到此,關(guān)于“Promise的原理和基礎(chǔ)用法介紹”的學(xué)習(xí)就結(jié)束了,希望能夠解決大家的疑惑。理論與實(shí)踐的搭配能更好的幫助大家學(xué)習(xí),快去試試吧!若想繼續(xù)學(xué)習(xí)更多相關(guān)知識(shí),請(qǐng)繼續(xù)關(guān)注億速云網(wǎng)站,小編會(huì)繼續(xù)努力為大家?guī)砀鄬?shí)用的文章!

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI