## Promise 的含義
Promise 是異步編程的一種解決方案,比傳統的解決方案——回調函數和事件——更合理和更強大。它由社區最早提出和實現,ES6 將其寫進了語言標準,統一了用法,原生提供了`Promise`對象。
所謂`Promise`,簡單說就是一個容器,里面保存著某個未來才會結束的事件(通常是一個異步操作)的結果。從語法上說,Promise 是一個對象,從它可以獲取異步操作的消息。Promise 提供統一的 API,各種異步操作都可以用同樣的方法進行處理。
`Promise`對象有以下兩個特點。
(1)對象的狀態不受外界影響。`Promise`對象代表一個異步操作,有三種狀態:`pending`(進行中)、`fulfilled`(已成功)和`rejected`(已失敗)。只有異步操作的結果,可以決定當前是哪一種狀態,任何其他操作都無法改變這個狀態。這也是`Promise`這個名字的由來,它的英語意思就是“承諾”,表示其他手段無法改變。
(2)一旦狀態改變,就不會再變,任何時候都可以得到這個結果。`Promise`對象的狀態改變,只有兩種可能:從`pending`變為`fulfilled`和從`pending`變為`rejected`。只要這兩種情況發生,狀態就凝固了,不會再變了,會一直保持這個結果,這時就稱為 resolved(已定型)。如果改變已經發生了,你再對`Promise`對象添加回調函數,也會立即得到這個結果。這與事件(Event)完全不同,事件的特點是,如果你錯過了它,再去監聽,是得不到結果的。
注意,為了行文方便,本章后面的`resolved`統一只指`fulfilled`狀態,不包含`rejected`狀態。
有了`Promise`對象,就可以將異步操作以同步操作的流程表達出來,避免了層層嵌套的回調函數。此外,`Promise`對象提供統一的接口,使得控制異步操作更加容易。
`Promise`也有一些缺點。首先,無法取消`Promise`,一旦新建它就會立即執行,無法中途取消。其次,如果不設置回調函數,`Promise`內部拋出的錯誤,不會反應到外部。第三,當處于`pending`狀態時,無法得知目前進展到哪一個階段(剛剛開始還是即將完成)。
如果某些事件不斷地反復發生,一般來說,使用[Stream](https://nodejs.org/api/stream.html)模式是比部署`Promise`更好的選擇。
## 基本用法
ES6 規定,`Promise`對象是一個構造函數,用來生成`Promise`實例。
下面代碼創造了一個`Promise`實例。
~~~javascript
const promise = new Promise(function(resolve, reject) {
// ... some code
if (/* 異步操作成功 */){
resolve(value);
} else {
reject(error);
}
});
~~~
`Promise`構造函數接受一個函數作為參數,該函數的兩個參數分別是`resolve`和`reject`。它們是兩個函數,由 JavaScript 引擎提供,不用自己部署。
`resolve`函數的作用是,將`Promise`對象的狀態從“未完成”變為“成功”(即從 pending 變為 resolved),在異步操作成功時調用,并將異步操作的結果,作為參數傳遞出去;`reject`函數的作用是,將`Promise`對象的狀態從“未完成”變為“失敗”(即從 pending 變為 rejected),在異步操作失敗時調用,并將異步操作報出的錯誤,作為參數傳遞出去。
`Promise`實例生成以后,可以用`then`方法分別指定`resolved`狀態和`rejected`狀態的回調函數。
~~~javascript
promise.then(function(value) {
// success
}, function(error) {
// failure
});
~~~
`then`方法可以接受兩個回調函數作為參數。第一個回調函數是`Promise`對象的狀態變為`resolved`時調用,第二個回調函數是`Promise`對象的狀態變為`rejected`時調用。其中,第二個函數是可選的,不一定要提供。這兩個函數都接受`Promise`對象傳出的值作為參數。
下面是一個`Promise`對象的簡單例子。
~~~javascript
function timeout(ms) {
return new Promise((resolve, reject) => {
setTimeout(resolve, ms, 'done');
});
}
timeout(100).then((value) => {
console.log(value);
});
~~~
上面代碼中,`timeout`方法返回一個`Promise`實例,表示一段時間以后才會發生的結果。過了指定的時間(`ms`參數)以后,`Promise`實例的狀態變為`resolved`,就會觸發`then`方法綁定的回調函數。
Promise 新建后就會立即執行。
~~~javascript
let promise = new Promise(function(resolve, reject) {
console.log('Promise');
resolve();
});
promise.then(function() {
console.log('resolved.');
});
console.log('Hi!');
// Promise
// Hi!
// resolved
~~~
上面代碼中,Promise 新建后立即執行,所以首先輸出的是`Promise`。然后,`then`方法指定的回調函數,將在當前腳本所有同步任務執行完才會執行,所以`resolved`最后輸出。
下面是異步加載圖片的例子。
~~~javascript
function loadImageAsync(url) {
return new Promise(function(resolve, reject) {
const image = new Image();
image.onload = function() {
resolve(image);
};
image.onerror = function() {
reject(new Error('Could not load image at ' + url));
};
image.src = url;
});
}
~~~
上面代碼中,使用`Promise`包裝了一個圖片加載的異步操作。如果加載成功,就調用`resolve`方法,否則就調用`reject`方法。
下面是一個用`Promise`對象實現的 Ajax 操作的例子。
~~~javascript
const getJSON = function(url) {
const promise = new Promise(function(resolve, reject){
const handler = function() {
if (this.readyState !== 4) {
return;
}
if (this.status === 200) {
resolve(this.response);
} else {
reject(new Error(this.statusText));
}
};
const client = new XMLHttpRequest();
client.open("GET", url);
client.onreadystatechange = handler;
client.responseType = "json";
client.setRequestHeader("Accept", "application/json");
client.send();
});
return promise;
};
getJSON("/posts.json").then(function(json) {
console.log('Contents: ' + json);
}, function(error) {
console.error('出錯了', error);
});
~~~
上面代碼中,`getJSON`是對 XMLHttpRequest 對象的封裝,用于發出一個針對 JSON 數據的 HTTP 請求,并且返回一個`Promise`對象。需要注意的是,在`getJSON`內部,`resolve`函數和`reject`函數調用時,都帶有參數。
如果調用`resolve`函數和`reject`函數時帶有參數,那么它們的參數會被傳遞給回調函數。`reject`函數的參數通常是`Error`對象的實例,表示拋出的錯誤;`resolve`函數的參數除了正常的值以外,還可能是另一個 Promise 實例,比如像下面這樣。
~~~javascript
const p1 = new Promise(function (resolve, reject) {
// ...
});
const p2 = new Promise(function (resolve, reject) {
// ...
resolve(p1);
})
~~~
上面代碼中,`p1`和`p2`都是 Promise 的實例,但是`p2`的`resolve`方法將`p1`作為參數,即一個異步操作的結果是返回另一個異步操作。
注意,這時`p1`的狀態就會傳遞給`p2`,也就是說,`p1`的狀態決定了`p2`的狀態。如果`p1`的狀態是`pending`,那么`p2`的回調函數就會等待`p1`的狀態改變;如果`p1`的狀態已經是`resolved`或者`rejected`,那么`p2`的回調函數將會立刻執行。
~~~javascript
const p1 = new Promise(function (resolve, reject) {
setTimeout(() => reject(new Error('fail')), 3000)
})
const p2 = new Promise(function (resolve, reject) {
setTimeout(() => resolve(p1), 1000)
})
p2
.then(result => console.log(result))
.catch(error => console.log(error))
// Error: fail
~~~
上面代碼中,`p1`是一個 Promise,3 秒之后變為`rejected`。`p2`的狀態在 1 秒之后改變,`resolve`方法返回的是`p1`。由于`p2`返回的是另一個 Promise,導致`p2`自己的狀態無效了,由`p1`的狀態決定`p2`的狀態。所以,后面的`then`語句都變成針對后者(`p1`)。又過了 2 秒,`p1`變為`rejected`,導致觸發`catch`方法指定的回調函數。
注意,調用`resolve`或`reject`并不會終結 Promise 的參數函數的執行。
~~~javascript
new Promise((resolve, reject) => {
resolve(1);
console.log(2);
}).then(r => {
console.log(r);
});
// 2
// 1
~~~
上面代碼中,調用`resolve(1)`以后,后面的`console.log(2)`還是會執行,并且會首先打印出來。這是因為立即 resolved 的 Promise 是在本輪事件循環的末尾執行,總是晚于本輪循環的同步任務。
一般來說,調用`resolve`或`reject`以后,Promise 的使命就完成了,后繼操作應該放到`then`方法里面,而不應該直接寫在`resolve`或`reject`的后面。所以,最好在它們前面加上`return`語句,這樣就不會有意外。
~~~javascript
new Promise((resolve, reject) => {
return resolve(1);
// 后面的語句不會執行
console.log(2);
})
~~~
- 文檔說明
- 大廠面試題
- HTML
- 001.如何遍歷一個dom樹
- 002.為什么操作DOM會很慢
- 003.瀏覽器渲染HTML的步驟
- 004.DOM和JavaScript的關系
- JS
- 001.數組扁平化并去重排序
- 002.高階函數
- 003.sort() 對數組進行排序
- 004.call 、 apply 和bind的區別
- 006.0.1+0.2為什么等于0.30000000000000004
- 011.var、let、const 的區別及實現原理?
- 010.new操作符都做了什么
- 009.a.b.c.d 和 a['b']['c']['d'],哪個性能更高?
- 016.什么是防抖和節流?有什么區別?如何實現?
- 017.['1', '2', '3'].map(parseInt) what & why ?
- 018.為什么 for 循環嵌套順序會影響性能?
- 019.介紹模塊化發展歷程
- 020.push輸出問題
- 021.判斷數組的三個方法
- 022.全局作用域中,用 const 和 let 聲明的變量不在 window 上,那到底在哪里?如何去獲取?
- 023.輸出以下代碼的執行結果并解釋為什么
- 024.ES6 代碼轉成 ES5 代碼的實現思路是什么
- 025.為什么普通 for 循環的性能遠遠高于 forEach 的性能,請解釋其中的原因。
- 026.數組里面有10萬個數據,取第一個元素和第10萬個元素的時間相差多少
- 027.變量類型
- 028.原型和原型鏈
- 029.作用域和閉包
- 030. 異步
- 031.ES6/7 新標準的考查
- 024.事件冒泡/事件代理
- 025.手寫 XMLHttpRequest 不借助任何庫
- 026.什么是深拷貝?
- 0027.克隆數組的方法
- 0028.ES6之展開運算符(...)
- 0029.arguments
- 0030. requestAnimationFrame
- 0031.遞歸爆棧問題與解決
- 021.簡單改造下面的代碼,使之分別打印 10 和 20
- 032.箭頭函數與普通函數
- 033.去除掉html標簽字符串里的所有屬性
- 034.查找公共父節點
- 035.Promise
- 0036.JSON.stringify ()
- CSS
- 001. BFC
- 002.介紹下 BFC、IFC、GFC 和 FFC
- 003.分析比較 opacity: 0、visibility: hidden、display: none 優劣和適用場景
- 004.怎么讓一個 div 水平垂直居中
- 005.重排重繪
- 006.inline/block/inline-block的區別
- 007.選擇器的權重和優先級
- 008.盒模型
- 009.清除浮動
- 010.flex
- 011.nth-child和nth-of-type的區別
- 0012.overflow
- 0013.CSS3中translate、transform和translation的區別和聯系
- 0014.flex
- 0015.px、em、rem
- 0016.width:100%
- 網絡
- 001.講解下HTTPS的工作原理
- 002.介紹下 HTTPS 中間人攻擊
- 003.談談你對TCP三次握手和四次揮手的理解
- 004.A、B 機器正常連接后,B 機器突然重啟,問 A 此時處于 TCP 什么狀態
- 005.簡單講解一下http2的多路復用
- 006. 介紹下 http1.0、1.1、2.0 協議的區別?
- 007.永久性重定向(301)和臨時性重定向(302)對 SEO 有什么影響
- 008.URL從輸入到頁面展示的過程
- 009.接口如何防刷
- 010.http狀態碼?
- 0111.跨域/如何解決?
- 012.cookie 和 localStorage 有何區別?
- 013.Fetch API
- 014.跨域Ajax請求時是否帶Cookie的設置
- 0015.協商緩存和強緩存
- 性能優化
- 001.前后端分離的項目如何seo
- 002.性能優化的方法
- 003.防抖和節流
- React
- 001.React 中 setState 什么時候是同步的,什么時候是異步的?
- 002.Virtual DOM 真的比操作原生 DOM 快嗎?談談你的想法。
- 003.Hooks 的特別之處
- 004.元素和組件有什么區別?
- 005.什么是 Pure Components?
- 006.HTML 和 React 事件處理有什么區別?
- 007.如何將參數傳遞給事件處理程序或回調函數?
- 008.如何創建 refs?
- 009.什么是 forward refs?
- 010.什么是 Virtual DOM?
- 011.什么是受控組件、非受控組件?
- 012.什么是 Fragments ?
- 013.為什么React元素有一個$$typeof屬性?
- 014.如何在 React 中創建組件?
- 015.React 如何區分 Class 和 Function?
- 016.React 的狀態是什么?
- 017.React 中的 props 是什么?
- 018.狀態和屬性有什么區別?
- 019.如何在 JSX 回調中綁定方法或事件處理程序?
- 020.什么是 "key" 屬性,在元素數組中使用它們有什么好處?
- 021.為什么順序調用對 React Hooks 很重要?
- 022.setState如何知道該做什么?
- 023.hook規則?
- 024.Hooks 與 Class 中調用 setState 有不同的表現差異么?
- 025.useEffect
- 026.fiber的作用
- 027.context的作用?
- 028.setState何時同步何時異步?
- 029.react性能優化
- 030.fiber
- 031.React SSR
- 異步
- 001.介紹下promise
- 002.Async/Await 如何通過同步的方式實現異步
- 003.setTimeout、Promise、Async/Await 的區別
- 004.JS 異步解決方案的發展歷程以及優缺點
- 005.Promise 構造函數是同步執行還是異步執行,那么 then 方法呢?
- 006.模擬實現一個 Promise.finally
- 012.簡單手寫實現promise
- 015.用Promise對象實現的 Ajax
- 007.簡單實現async/await中的async函數
- 008.設計并實現 Promise.race()
- 009.Async/await
- 010.珠峰培訓promise
- git
- 001.提交但沒有push
- 002.gitignore沒有作用?
- Node
- 001.用nodejs,將base64轉化成png文件
- Koa
- 001.koa和express的區別
- 數據庫
- redux
- 001.redux 為什么要把 reducer 設計成純函數
- 002.在 React 中如何使用 Redux 的 connect() ?
- 003.mapStateToProps() 和 mapDispatchToProps() 之間有什么區別?
- 004.為什么 Redux 狀態函數稱為 reducers ?
- 005.如何在 Redux 中發起 AJAX 請求?
- 006.訪問 Redux Store 的正確方法是什么?
- 007.React Redux 中展示組件和容器組件之間的區別是什么?
- 008.Redux 中常量的用途是什么?
- 009.什么是 redux-saga?
- 設計模式
- 公司題目
- 001.餓了么
- 001.div垂直水平居中(flex、絕對定位)
- 002.React子父組件之間如何傳值
- 003.Emit事件怎么發,需要引入什么
- 004.介紹下React高階組件,和普通組件有什么區別
- 005.一個對象數組,每個子對象包含一個id和name,React如何渲染出全部的name
- 006.在哪個生命周期里寫
- 007.其中有幾個name不存在,通過異步接口獲取,如何做
- 008.渲染的時候key給什么值,可以使用index嗎,用id好還是index好
- 009.webpack如何配sass,需要配哪些loader
- 010.配css需要哪些loader
- 011.如何配置把js、css、html單獨打包成一個文件
- 012.監聽input的哪個事件,在什么時候觸發
- 013.兩個元素塊,一左一右,中間相距10像素
- 014.上下固定,中間滾動布局如何實現
- 016.取數組的最大值(ES5、ES6)
- 017.apply和call的區別
- 018.ES5和ES6有什么區別
- 019.some、every、find、filter、map、forEach有什么區別
- 020.上述數組隨機取數,每次返回的值都不一樣
- 021.如何找0-5的隨機數,95-99呢
- 022.頁面上有1萬個button如何綁定事件
- 023.如何判斷是button
- 024.頁面上生成一萬個button,并且綁定事件,如何做(JS原生操作DOM)
- 025.循環綁定時的index是多少,為什么,怎么解決
- 026.頁面上有一個input,還有一個p標簽,改變input后p標簽就跟著變化,如何處理
- 瀏覽器相關
- 001.性能優化
- 002.web安全
- 003.獲取瀏覽器大小
- 004.從輸入 URL 到頁面加載完成的過程中都發生了什么事情?
- 后端
- 001.分布式
- zuku
- 字節
- webpack
- webpack的打包原理是什么
- Webpack-- 常見面試題
- webscoket