入門Promise的正確姿勢

来源:https://www.cnblogs.com/yuliangbin/archive/2018/03/18/8592724.html
-Advertisement-
Play Games

Promise是非同步編程的一種解決方案,從語法上說,Promise是一個對象,從它可以獲取非同步操作的消息。 Promise的基本用法 Promise構造函數接受一個函數作為參數,該函數的兩個參數分別是resolve和reject。它們是兩個函數,由JavaScript引擎提供。 resolve函數的 ...


Promise是非同步編程的一種解決方案,從語法上說,Promise是一個對象,從它可以獲取非同步操作的消息。

Promise的基本用法

Promise構造函數接受一個函數作為參數,該函數的兩個參數分別是resolve和reject。它們是兩個函數,由JavaScript引擎提供。

  • resolve函數的作用是,將Promise對象的狀態從“未完成”變為“成功”(即從Pending變為Resolved),在非同步操作成功時調用,並將非同步操作的結果作為參數傳遞出去。
  • reject函數的作用是,將Promise對象的狀態從“未完成”變為“失敗”(即從Pending變為Rejected),在非同步操作失敗時調用,並將非同步操作報出的錯誤作為參數傳遞出去。
  • then方法可以接受兩個回調函數作為參數。第一個回調函數是Promise對象的狀態變為Resolved時調用,第二個回調函數是Promise對象的狀態變為 Reject時調用
var promise = new Promise(
    //非同步執行,Promise對象創建後會被立即執行
    function (resolve,reject) {
        //耗時很長的非同步操作
  if('非同步處理成功') {  
        resolve();    //數據處理成功時調用
  } else {
        reject();    //數據處理失敗時調用
    }
        
    }
)
//Promise實例生成以後,可以用then方法分別指定Resolved狀態和Reject狀態的回調函數。
promise.then(
    function A() {
        //數據處理成功後執行
    },
    function B() {
        //數據處理失敗後執行
    }
)

 

下麵我們舉一個簡單的例子來模擬一下非同步操作成功和非同步操作失敗函數的運行過程。

 

console.log('starting');
var promise = new Promise(function(resolve, reject) {  
    setTimeout(function() { 
        console.log("2秒後,我運行了");
        resolve('非同步操作成功了');     //1
        //reject('非同步操作失敗了');    //2
     //return 'hello';       //3
}, 2000) }).then(function (value) { console.log('非同步操作成功後執行我:',value); }, function (value) { console.log('非同步操作失敗後執行我:',value); } ) console.log('我也運行了'); // 上面的代碼中1處代碼的調用,輸出順序是: //starting //我也運行了
//2秒後,我運行了 // 非同步操作成功後執行我: 非同步操作成功了 // 上面的代碼中2處代碼的調用,輸出順序是: //starting //我也運行了
//2秒後,我運行了 // 非同步操作失敗後後執行我: 非同步操作失敗了


//上面的代碼中3處代碼的調用,輸出順序是:
//starting
//我也運行了
//2秒後,我運行了

知代碼3處的return 'hello' 語句在新建的new Promise對象中並沒有被當作參數返回給then()函數內.那麼會不會返回給promise了呢?我們用一段代碼來測試一下

console.log('starting');
var promise = new Promise(function(resolve, reject) {  
    setTimeout(function() { 
        console.log("2秒後,我運行了");
        resolve('非同步操作成功了');     //1
        //reject('非同步操作失敗了');    //2
        return 'hello';
    }, 2000) 
    
})
promise.then(function (value) { 
    console.log('非同步操作成功後執行我:',value);
},
function (value) {
    console.log('非同步操作失敗後執行我:',value);
}
)
console.log('我也運行了');
console.log(promise);
setTimeout(function () {
    console.log('5秒後,我執行了');
    console.log(promise);
},5000);


//starting
//我也運行了
//Promise { pending }
  //[[PromiseStatus]]:"pending"
  //[[PromiseValue]]:undefined
  //__proto__:Promise {constructor: , then: , catch: , …}
//2秒後,我運行了
//非同步操作成功後執行我: 非同步操作成功了
//5秒後,我執行了
//Promise { resolved }
  //[[PromiseStatus]]:"resolved"
  //[[PromiseValue]]:"非同步操作成功了"
  //__proto__:Promise {constructor: , then: , catch: , …}

由執行結果可知,變數promise仍然是new Promise對象的一個實例。所以return語句雖然被執行了,但對promise實例不會產生任何影響,相當於不存在。

由上面測試的代碼可知,Promise對象有以下兩個特點。
  (1)對象的狀態不受外界影響。Promise對象代表一個非同步操作,有三種狀態:Pending(進行中)、Resolved(已完成,又稱Fulfilled) 和Rejected(已失敗)。只有非同步操作的結果,可以決定當前是哪一種狀態,

  (2)一旦狀態改變,就不會再變,任何時候都可以得到這個結果。Promise對象的狀態改變,只有兩種可能:從Pending變為Resolved和從Pending變 為Rejected。只要這兩種情況發生,狀態就凝固了,不會再變了,會一直保持這個結果。就算改變已經發生了,你再對Promise對象添加回調函數,也會立即得到這個結果。這與事件(Event)完全不同,事件的特點是,如果你錯過了它,再去監聽,是得不到結果的。

resolve(value) VS resolve(Promise)

我們會在非同步操作成功時調用resolve函數,其作用是將Promise對象的狀態從Pending變為Resolved,並將非同步操作的結果作為參數傳遞給then()方法里的第一個函數的形參

那麼傳入的參數是值和傳入的參數是promise對象時有什麼不同呢。我們來看一下例子。

當傳入的參數為值時:

var time = new Date();
var promise = new Promise(function(resolve, reject) {  
    setTimeout(function() { 
        console.log("1秒後,我運行了");
        resolve('非同步操作成功了');     //1
    }, 2000) 
    
}).then(function (value) {
    console.log(value,new Date() - time);
})
//執行的輸出結果為:
//2秒後,我運行了
//非同步操作成功了 1002

大約過了一秒左右,我們可以看到在resolved狀態的回調方法中,我們列印出了上面註釋中的內容。我們能夠通過resolve方法傳遞操作的結果,然後在回調方法中使用這些結果。

如果我們在resolve中傳入一個Promise實例呢?

var time = new Date();
var promise = new Promise(function(resolve, reject) {  
    setTimeout(function() { 
        console.log("2秒後,我運行了");
        resolve('非同步操作成功了');     //1
    }, 2000) 
    
})

var promise2 = new Promise(function (resolve,reject) {
    setTimeout(resolve,1000,promise);
}).then(function (value) {
    console.log(value,new Date() - time);
})

//執行後輸出的結果為:
//
2秒後,我運行了 //非同步操作成功了 2003

promise2經過了2秒後才列印出來結果。奇怪了,我們不是設置promise2經過1秒後執行嗎?

簡單說就是因為promise2中的resolve()函數傳入了promise對象,此時promise對象的狀態決定了promise的狀態,同時會把返回值傳給promise。

Promise/A+中規定 [[Resolve]](promise, x)

2.3.2.如果x是一個promise實例, 則以x的狀態作為promise的狀態

  2.3.2.1.如果x的狀態為pending, 那麼promise的狀態也為pending, 直到x的狀態變化而變化。

  2.3.2.2.如果x的狀態為fulfilled, promise的狀態也為fulfilled, 並且以x的不可變值作為promise的不可變值。

  2.3.2.3.如果x的狀態為rejected, promise的狀態也為rejected, 並且以x的不可變原因作為promise的不可變原因。

2.3.4.如果x不是對象或函數,則將promise狀態轉換為fulfilled並且以x作為promise的不可變值。

 Promise.prototype.then()

Promise實例具有then方法,也就是說,then方法是定義在原型對象Promise.prototype上的。它的作用是為Promise實例添加狀態改變時的回調函數。 前面說過,then方法的第一個參數是Resolved狀態的回調函數,第二個參數(可選)是Rejected狀態的回調函數。

then方法返回的是一個新的Promise實例(註意,不是原來那個Promise實例)。因此可以採用鏈式寫法,即then方法後面再調用另一個then方法。

var promise = new Promise(function(resolve, reject) {  
    setTimeout(function() { 
        console.log("2秒後,我運行了");
        resolve('非同步操作成功了');     //1
    }, 2000) 
    
})
promise.name = 'promise';
console.log('promise:',promise)
var promise2 = promise.then(function (value) {
    console.log(value);
})
promise2.name = 'promise2';
console.log('promise2:',promise2);

// promise:
//     Promise { pending }
//     [[PromiseStatus]]:"pending"
//     [[PromiseValue]]:undefined
//     name:"promise"
//     __proto__:Promise {constructor: , then: , catch: , …}
// promise2:
// Promise { pending }
//     [[PromiseStatus]]:"pending"
//     [[PromiseValue]]:undefined
//     name:"promise2"
//     __proto__:Promise {constructor: , then: , catch: , …}

 

我們可以知道promise.then()方法執行後返回的是一個新的Promise對象。也就是說上面代碼中的promise2是一個Promise對象,它的實現效果和下麵的代碼是一樣的,只不過在then()方法里,JS引擎已經自動幫我們做了。

 

promise2 = new Promise(function (resolve,reject) {})

 

既然在then()函數里已經自動幫我實現了一個promise對象,但是我要怎麼才能給resolve()或reject()函數傳參呢?其實在then()函數里,我們可以用return()的方式來給promise2的resolve()或reject()傳參。看一個例子。

//var time = new Date();
var promise = new Promise(function(resolve, reject) {  
    setTimeout(function() { 
        console.log("2秒後,我運行了");
        resolve('非同步操作成功了');     //1
        //reject('非同步操作失敗了');    //2
    }, 2000) 
    
})
//console.log('promise:',promise)
var promise2 = promise.then(function (value) {
    console.log(value);
    //resolve('nihao');  //報錯。註意,這裡不能寫resolve()方法,因為在then函數里是沒有resolve方法的
    return (1);       
    //return promise;   //也可以return一個promise對象,返回promise對象執行後resolve('參數值')或reject('參數值')內部的參數值
  //如果不寫return的話,預設返回的是return undefined 。
}) var promise3 = promise2.then(function (value) { console.log('is:',value); },function (value) { console.log('error:',value); }) promise2.name = 'promise2'; setTimeout(function () { console.log(promise2); },3000); //2秒後,我運行了 //非同步操作成功了 //is: 1 //Promise {resolved} //name:"promise2" //__proto__:Promise //[[PromiseStatus]]:"resolved" //[[PromiseValue]]:1

Promise與錯誤狀態處理

.then(null, rejection),用於指定非同步操作發生錯誤時執行的回調函數。下麵我們做一個示例。

var promise = new Promise(function(resolve, reject) {
    setTimeout(function () {
            reject('error');
    },2000);
}).then(null,function(error) {
    console.log('rejected', error)
});
//rejected error

我們知道then()方法執行後返回的也是一個promise對象,因此也可以調用then()方法,但這樣的話為了捕獲異常信息,我們就需要為每一個then()方法綁定一個.then(null, rejection)。由於Promise對象的錯誤信息具有“冒泡”性質,錯誤會一直向後傳遞,直到被捕獲為止。因此Promise為我們提供了一個原型上的函數Promise.prototype.catch()來讓我們更方便的 捕獲到異常。

我們看一個例子

 

var promise = new Promise(function(resolve, reject) {
    setTimeout(function () {
            reject('error');
    },2000);
}).then(function(value) {
    console.log('resolve', value);
}).catch(function (error) {
    console.log(error);
})
//運行結果
//error

上面代碼中,一共有二個Promise對象:一個由promise產生,一個由then產生。它們之中任何一個拋出的錯誤,都會被最後一個catch捕獲。

但是如果用.then(null, rejection)方法來處理錯誤信息,我們需要在每一個rejection()方法中返回上一次異常信息的狀態,這樣當調用的then()方法一多的時候,對會對代碼的清晰性和邏輯性造成影響。

所以,一般來說,不要在then方法裡面定義Reject狀態的回調函數(即then的第二個參數),總是使用catch方法。

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • vscode是微軟開發的的一款代碼編輯器,就如官網上說的一樣,vscode重新定義(redefined)了代碼編輯器。當前市面上常用的輕型代碼編輯器主要是:sublime,notepad++,editplus,atom這幾種。比起notepad++、editplus,vscode集成了許多IDE才具 ...
  • vue 點我 ... ...
  • 腳本語言最重要的幾個部分: 數據類型 運算符 控制語句 數組 方法(函數) 一、基礎知識 關鍵字:系統定義 有意義的名字如 background link 等 標識符:自己定 比如class的名字aa 變數:可以看作儲存數據的容器,名字必須用字母開頭,對大小寫敏感。定義後可多次賦值 傳值賦值。 定義 ...
  • 1.寫HTML 訪問方式: http://localhost:8080/myweb/hellow.html (註意:8080埠要加上) 本地電腦:(HP-PC) D:\tomact\webapps\myweb(網頁寫到這裡) ...
  • 上圖是今天下午5個小時的解決成果 1:安裝軟體要耐心,此次安裝軟體第一個星期安裝時正常是可以用的接著不能用了第二個星期,然後我天天就想為什麼,其實不然也沒事嗎,可能軟體太多導致, 最終感覺還是基礎功不牢固而導致的;第二個星期天最後一天的晚上19.32裝好的; 2.開始安裝,對於我們開發者來說java ...
  • 一、什麼是緩存 一個緩存就是一個組件,它可以透明地存儲數據,以便未來可以更快地服務於請求。 緩存能夠服務的請求越多,整體系統性能就提升得越多。 二、Angular 中的緩存 2.1 $cacheFactory 簡介 $cacheFactory 是一個為所有Angular服務生成緩存對象的服務。在內部 ...
  • 概況: 通過本篇文章你可以對react的重點有個整體的認識。 關於react是什麼,優點,解決什麼問題等,網上一大推就不啰嗦了。 瞭解虛擬DOM的實現,參考這篇文章 簡單講,其實就是用一個輕量級的dom結構(用js模擬實現),來模擬重量級的dom結構,通過比對輕量級結構後,在操作重量級dom結構提高 ...
  • 註意:此文並不是把vue改為全部替換為ts,而是可以在原來的項目中植入ts文件,目前只是實踐階段,向ts轉化過程中的過渡。 ts有什麼用? 類型檢查、直接編譯到原生js、引入新的語法糖 為什麼用ts? TypeScript的設計目的應該是解決JavaScript的“痛點”:弱類型和沒有命名空間,導致 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...