前言:你們好,我叫邵威儒,你們都喜歡喊我小邵,學的金融專業卻憑藉興趣愛好入了程序猿的坑,從大學買的第一本vb和自學vb,我就與編程結下不解之緣,隨後自學易語言寫遊戲輔助、交易軟件,至今進入了前端領域,看到很多朋友都寫文章分享,本身也弄一個玩玩,如下文章純屬我的理解,便於記錄學習,確定有理解錯誤或理解不到位的地方,意在站在前輩的肩膀,分享我的對技術的通俗理解,共同成長!javascript
後續我會陸陸續續更新javascript方面,儘可能把javascript這個學習路徑體系都寫一下
包括前端所經常使用的es六、angular、react、vue、nodejs、koa、express、公衆號等等
都會從淺到深,從入門開始逐步寫,但願能讓你們有所收穫,也但願你們關注我~前端
源碼地址:github.com/iamswr/prom…
文章列表:juejin.im/user/5a84f8…vue
Author: 邵威儒
Email: 166661688@qq.com
Wechat: 166661688
github: github.com/iamswr/java
JavaScript做爲單線程語言,其特色也是其缺陷,特色就是不用處理多線程引起的佔用資源、衝突啪啦啪啦等,缺陷就是同一時間,只能作一件事情,那麼會存在一個問題,網絡傳輸是有延遲的,好比A發一條信息到B服務器,在B服務器還沒返回信息給A時,那麼A就會一直在等待接收信息,會形成頁面的假死,那麼該怎麼辦?俗話說得好,程序猿改變世界,因而乎出現了異步的概念,我會分如下幾點,去講述我對前端異步的理解:node
回調函數被認爲是一種高級函數,一種被做爲參數傳遞給另外一個函數(在這稱做"otherFunction")的高級函數,回調函數會在otherFunction內被調用(或執行)。回調函數的本質是一種模式(一種解決常見問題的模式),所以回調函數也被稱爲回調模式。react
是否是看起來一頭懵逼,到底什麼是回調函數?有什麼做用?我對回調函數理解,從真正意義上讓我忽然間恍然大悟的,就是當初研究jQuery底層源碼的時候,咱們看如下一段代碼:jquery
<img src='../a.jpg'></img>
<img src='../b.jpg'></img>
<img src='../c.jpg'></img>
$("img").attr("title",function(index,attr){
console.log(index) // 依次返回0 1 2
});
複製代碼
在attr方法的第二個參數,傳入了一個function,而該函數,會依次獲取$('img')
的DOM對象對應index和attr,咱們能夠在該function裏,寫咱們須要的業務邏輯,那麼這樣有什麼好處呢?git
個人理解是,假如我要封裝一個庫,造一個輪子,那麼要考慮到通用性和複用性而且提供一個途徑,讓使用者任意發揮想象寫業務邏輯,而且把相關可能使用到的參數,都傳給使用者。es6
假設,咱們如今有一個需求,要寫一個判斷類型的方法,常見的方法有如下幾種:github
- typeOf // 簡單的數據類型判斷,棧區
- instanceof // 複雜的數據類型,堆區
- constructor // 複雜的數據類型,主要是用在繼承的改寫指向的構造函數,不多用於判斷類型
- Object.prototype.toString.call() // 絕大多數庫底層都是使用該方式,返回值如[object String]
複製代碼
首先,咱們寫一個isType方法
function isType(content,type,fn){
// 類型判斷
let t = Object.prototype.toString
.call(content)
.replace(/\[object\s|\]/g,'')
// 判斷完成後,執行傳入的callback函數
fn(type,t)
}
複製代碼
如今咱們要判斷一個值的類型,而後拿到這個類型,最終執行咱們須要作的事情
isType('hello swr','String',function(type,t){ // 做爲參數傳入的函數,接收isType函數內的fn中type和t這兩個參數
console.log(type === t) // true
})
複製代碼
那麼問題就出現了,好比咱們使用node.js的時候,進行文件讀取操做時,想獲取的值是一種嵌套依賴關係時,會出現什麼問題呢?
目錄結構:
- iamswr
- A.txt
- B.txt
- C.txt
其中
A.txt文件裏的內容爲字符串B.txt
B.txt文件裏的內容爲字符串C.txt
C.txt文件裏的內容爲字符串'hello swr'
那麼當咱們想獲取到'hello swr',會遇到什麼問題呢?請看下面的代碼
let fs = require('fs')
fs.readFile('A.txt','utf8',function(err,data){ // 此時回調函數data值爲'B.txt'
fs.readFile(data,'utf8',function(err,data){ // 此時回調函數data值爲'C.txt'
fs.readFile(data,'utf8',function(err,data){
console.log(data) // 'hello swr'
})
})
})
複製代碼
以上這個例子若是嵌套依賴層次更高一些,那代碼變得十分難維護以及難閱讀,咱們在企業開發當中,常常會遇到想獲得的數據,是經過嵌套依賴的關係,最終纔得到須要的數據,陷入了回調地獄,而es6中,promise解決了這個讓前端頭疼的問題,後面我會詳細講promise,下面咱們先了解一下閉包、高階函數。
我我的理解,閉包其實是一種函數,因此閉包技術也是函數技術的一種;閉包能作的事情函數幾乎都能作,閉包有最大的兩個用處,一個是能夠讀取函數內部的變量,另外一個就是讓這些變量的值始終保持在內存中。
在javascript中,若是一個對象不被引用了,那麼這個對象會被GC回收,不然則一直保留在內存中,那麼利用這個特色,配合閉包使用,有如下幾個優勢:封閉做用域、保存做用域、做用域鏈條。
不污染全局變量,當團隊協做時,好比A大佬,封裝了jQuery庫,而jQuery庫內是有大量變量,若是不使用閉包,則jQuery庫內的變量會污染整個項目,甚至和其餘團員的變量有衝突
外部沒法獲取閉包內的變量,封閉了做用域
(function(){
var str = 'hello swr'
console.log(str) // 'hello swr'
})()
console.log(str) // 報錯
咱們用原生js來寫代碼的時候,會存在一個問題,
好比有5個button標籤
var btns = document.getElementsByTagName('button');
for(var i=0; i< btns.length; i++){
var btn = btns[i];
btn.onclick = function () {
alert('點擊了第' + i + '個按鈕');
}
}
不管咱們點擊哪一個button,都是彈出'點擊了第5個按鈕',
由於btn.onclick事件是異步觸發的,當事件被觸發時,
for循環早已經結束,此時變量I的值已是5,
全部onclick事件函數從內到外查找變量i時,查找到的值老是5。
能夠經過封閉做用域把每次循環的i值都封閉起來,
當時間函數順着做用域鏈從內到外查找變量i時,
會先找到被封閉在閉包環境中的i,
若是有5個按鈕, 則i的值就是0,1,2,3,4
var btns = document.getElementsByTagName('button');
for(var i=0; i< btns.length; i++){
(function (i) {
var btn = btns[i];
btn.onclick = function () {
alert('點擊了第' + i + '個按鈕');
}
})(i);
}
複製代碼
咱們知道,在es6以前,只有函數是有做用域的說法,在es6出現了,則有了塊級做用域的說法,好比
(function person(){
var name = '邵威儒'
console.log(name) // '邵威儒'
})()
console.log(name) // 報錯
複製代碼
在函數外部,是訪問不了內部的name,這就是做用域。 在es6出了一個新的概念,就是塊級做用域
{
let name = '邵威儒'
console.log(name) // '邵威儒'
}
console.log(name) // 報錯
複製代碼
效果和閉包同樣
函數嵌套函數,那麼內部的那個函數將造成做用域閉包。簡單的說,這種閉包可以達到的好處就是讓指令可以綁定一些全局數據去運行,優勢是全局數據隱藏化、 將數據綁定在指令上運行,讓指令再也不依賴全局數據。
function plus(num){
++num
return function(){
console.log(num)
}
}
let toPlus = plus(5)
此時toPlus實際上爲
function(){
console.log(num)
}
而這個num實際上就是plus函數內做用域的num,此時咱們沒法從外部修改num,並且把plus函數內的數據隱藏化,將數據綁定在toPlus上運行。
複製代碼
好比說,咱們實際開發中會遇到一個問題,就是某個函數,要等多個異步執行完畢後才執行,這種狀況怎麼作呢?
通常會想到如下這個辦法
let fs = require('fs')
let arr = []
fs.readFile('./a.txt','utf8',function(err,data){
arr.push(data) // 假設data爲'hello'
})
fs.readFile('./b.txt','utf8',function(err,data){
arr.push(data) // 假設data爲'swr'
})
console.log(arr) // 咱們但願打印出來是['hello','swr']或['swr','hello'],可是打印出來的倒是[]
這是爲何呢?
是由於javascript執行原理,是先執行同步,再執行異步的,而fs.readFile方法屬於異步方法,因此還沒執行完畢,就已經執行了console.log(arr)了
複製代碼
對於這種並不是依賴嵌套獲取,咱們稱爲「同步」獲取,此同步非異步同步的那個同步,特別是這種異步請求的數據,獲取到的時間前後順序不一樣,那咱們該如何實現「同步」獲取呢?
let fs = require('fs')
function after(times,callback){
let arr = []
return function(data){
arr.push(data)
if(--times === 0){
callback(arr)
}
}
}
let fn = after(2,function(arr){
console.log(arr) // 當fn執行兩次後,則會執行該回調函數
})
fs.readFile('./a.txt','utf8',function(err,data){
fn(data) // 假設data爲'hello'
})
fs.readFile('./b.txt','uft8',function(err,data)=>{
fn(data) // 假設data爲'swr'
})
最終當2個fs.readFile讀取完畢後,執行了fn()達到2次時,則會打印出['hello','swr']或者['swr','hello']
複製代碼
雖然以上的方式,實現了咱們須要的需求,可是問題來了,難道咱們每一次都要特地寫一個after函數嗎?其實還有一個概念,叫作發佈訂閱,訂閱就相似你收藏了這個電臺,而發佈,則是這個電臺向全部收藏了本電臺的粉絲進行廣播,看下面代碼
let fs = require('fs')
let event = {
arr:[], // 存須要執行的函數
result:[], // 存結果
on(fn){ // 訂閱
this.arr.push(fn)
},
emit(data){ // 發佈
this.result.push(data)
this.arr.forEach(fn=>fn(this.result))
}
}
event.on(function(data){
if(data.length === 2){
console.log(data) // ['hello','swr'] 或者 ['swr','hello']
}
})
fs.readFile('./a.txt','utf8',(err,data)=>{
event.emit(data) // data爲'hello'
})
fs.readFile('./b.txt','utf8',(err,data)=>{
event.emit(data) // data爲'swr'
})
當兩個fs.readFile讀取完成,而且在其回調函數內執行了event.emit,最終會打印出['hello','swr'] 或者 ['swr','hello']
複製代碼
囉囉嗦嗦說了那麼多,主要是想你們瞭解一下回調函數以及閉包,由於這概念和promise的緊密關聯的,promise部分我主要是想和你們根據promiseAplus規範,逐步手寫一個promise的底層實現方式。
首先,promise怎麼理解?我在知乎上看到一篇比較通俗易懂的小故事,你們能夠看看,zhuanlan.zhihu.com/p/19622332
早上,老爸說:「兒子,天氣如何?」 每週一早上,老爸問兒子下午的天氣狀況,兒子能夠到自家房子旁邊小山上使用望遠鏡來觀看。兒子在出發時許諾(Promise)老爸(會通知老爸天氣狀況)。 此刻,老爸決定,若是天氣不錯,明天就出去捕魚,不然就不去。並且若是兒子沒法得到天氣預報的話,也不去捕魚。 30分鐘左右,兒子回來了,每週的結局都不同。
結局A:成功得到了(retrieved)天氣預報,晴天 :) 兒子成功獲取了天氣預報,天空晴朗,陽光明媚!承諾(Promise)兌現了(resolved),因而老爸決定開始爲週日的捕魚作準備。
結局B:一樣成功得到了天氣預報,雨天:( 兒子成功得到了天氣預報,只不過是烏雲密佈,要下雨。承諾(Promise)兌現了(resolved),只是老爸決定呆在家裏,由於天氣很糟糕。
結局C:無法得到天氣預報:-/ 出了問題,兒子無法得知天氣預報,由於霧很大,就算站在小山上也沒法看清。兒子沒辦法對象他離開時許下的諾言, promise was rejected!老爸決定留下來,這並不值得冒險。
首先咱們要了解PromiseA+規範 promisesaplus.com/
// Promise構造函數的第一個參數爲executor
let promise = new Promise(function(resolve,reject){
console.log('我是會被當即執行的喲')
})
// promise的實例都有then方法
promise.then(()=>{ // 成功的回調
},()=>{ // 失敗的回調
})
複製代碼
// 默認時爲pending態,既不會走成功的回調也不會走失敗的回調
promise.then(()=>{
console.log('success1')
},()=>{
console.log('error1')
})
console.log('2')
在這段代碼中,只會打印出'2',由於promise一直處於pending態,不會走then後的回調函數
複製代碼
let promise = new Promise(function(resolve,reject){
console.log('1')
resolve() // 更改pending狀態爲resolved
})
promise.then(()=>{
console.log('success1')
},()=>{
console.log('error1')
})
console.log('2')
此時輸出順序爲'1' -> '2' -> 'success1'
複製代碼
let promise = new Promise(function(resolve,reject){
console.log('1')
setTimeout(()=>{ // 異步行爲
resolve() // 更改狀態爲成功
},1000)
})
promise.then(()=>{
console.log("success1")
})
promise.then(()=>{
console.log('success2')
})
console.log("2")
此時輸出順序爲'1' -> '2' -> 'success1' -> 'success2'
複製代碼
let promise = new Promise(function(resolve,reject){
throw new Error('出錯了') // 拋出錯誤
})
promise.then(()=>{
console.log('success1')
},()=>{
console.log('error1')
})
此時輸出爲 'error1'
複製代碼
下面代碼部分和源碼實現部分要結合來看
// ----- 代碼部分
// 1.executor默認在new的時候會自動執行
// 成功和失敗的視乎能夠傳遞參數
let promise = new Promise((resolve,reject)=>{ // 6.resolve、reject函數對應源碼實現部分的resolve、reject函數
resolve('hello swr') // 11.執行resolve
})
// 7.Promise的實例都有then方法
promise.then((data)=>{ // 8.成功的回調函數
},(err)=>{ // 9.失敗的回調函數
})
複製代碼
// ----- 源碼實現部分
// 2.聲明一個Promise構造函數
function Promise(executor){
let self = this
self.value = undefined
self.reason = undefined // 12.由於value和reason值須要在Promise實例方法then中使用,因此把這兩個值,賦給new出來的實例
function resolve(value){ // 3.聲明一個resolve函數
self.value = value // 13.當調用了resolve而且傳參數時,則把這value值賦予self.value
}
function reject(reason){ // 4.聲明一個reject函數
self.reason = reason // 13.當調用了reject而且傳參數時,則把這reason值賦予self.reason
}
executor(resolve,reject) // 5.把resolve、reject函數傳到executor
}
// 由於Promise的實例都有then方法,那麼意味着then方法是在Promise的原型對象中的方法
// 10.對應上面成功的回調函數onFulfilled以及失敗的回調函數onRejected
Promise.prototype.then = function(onFulfilled,onRejected){
}
module.exports = Promise // 把Promise暴露出去
複製代碼
此時,咱們會發現,如何去判斷調用resolve仍是reject呢? 這個時候咱們在內部應該維護一個狀態,而咱們以前說過了Promise有三種狀態,分別爲pending、resolved、rejected,那麼咱們接着看下面的代碼。
// ----- 代碼部分
let promise = new Promise((resolve,reject)=>{
resolve('hello swr') // 5.暫時忽略此行
resolve('看看同時執行resolve和reject會發生什麼?') // 5.此行執行resovle
reject('看看同時執行resolve和reject會發生什麼?') // 5.此行執行reject
})
promise.then((data)=>{
console.log('success:' + data) // 5.當調用了resolve函數,則輸出success:hello swr
},(err)=>{
})
複製代碼
// ----- 源碼實現部分
function Promise(executor){
let self = this
self.value = undefined
self.reason = undefined
self.status = 'pending' // 1.在內部維護一個status狀態
function resolve(value){
self.value = value
self.status = 'resolved' // 2.當調用了resolve時,更改狀態爲resolved
}
function reject(reason){
self.reason = reason
self.status = 'rejected' // 2.當調用了reject時,更改狀態爲rejected
}
executor(resolve,reject)
}
Promise.prototype.then = function(onFulfilled,onRejected){
let self = this
// 3.當咱們在then中,執行了成功或者失敗的回調函數時,首先要判斷目前處於什麼狀態
if(self.status === 'resolved'){
onFulfilled(self.value) // 4.當調用了resolve函數後,會執行成功的回調函數,而且把resolve中傳遞的值,傳遞給成功的回調函數
}
if(self.status === 'rejected'){
onRejected(self.reason) // 4.當調用了reject函數後,會執行成功的回調函數,而且把reject中傳遞的值,傳遞給失敗的回調函數
}
}
module.exports = Promise
複製代碼
當咱們在上面5中同時執行resolve和reject,會發現都可以執行,那麼就違背了狀態只能更改一次的原則了,下面咱們來解決這個問題。
// ----- 代碼部分
let promise = new Promise((resolve,reject)=>{
resolve('看看同時執行resolve和reject會發生什麼?') // 1. 此時執行resolve和reject
reject('看看同時執行resolve和reject會發生什麼?') // 3.此時即便調用reject,由於resolve已經調用了一次,從pending更改成resolve,因此在第一次調用後,屢次調用也不會生效
// 4.以上resolve、reject暫時忽略掉,咱們考慮一個狀況,當promise拋出錯誤時,怎麼去處理呢?
throw new Error('出錯啦')
})
promise.then((data)=>{
console.log('success:' + data)
},(err)=>{
})
複製代碼
// ----- 源碼實現部分
function Promise(executor){
let self = this
self.value = undefined
self.reason = undefined
self.status = 'pending'
function resolve(value){
if(self.status === 'pending'){ // 2.此時新增一個狀態判斷,當狀態爲pending的時候才能執行
self.value = value
self.status = 'resolved'
}
}
function reject(reason){
if(self.status === 'pending'){ // 2.此時新增一個狀態判斷,當狀態爲pending的時候才能執行
self.reason = reason
self.status = 'rejected'
}
}
// 5.當咱們在執行executor時,內部拋出錯誤的時候,能夠利用try catch來處理這個問題
try{
executor(resolve,reject)
}catch(error){
reject(error)
}
}
Promise.prototype.then = function(onFulfilled,onRejected){
let self = this
if(self.status === 'resolved'){
onFulfilled(self.value)
}
if(self.status === 'rejected'){
onRejected(self.reason)
}
}
module.exports = Promise
複製代碼
這樣咱們就解決了屢次調用,只認第一次的更改狀態,而且當拋出錯誤時,使用try catch來處理,那麼接下來,咱們想一下,目前咱們都是new一個Promise,而後調用then,這整個流程,彷彿沒任何問題,可是,如今問題出現了,若是此時resolve或者reject是處於setTimeout(()=>{resolve()},3000)中,即處於異步中,當咱們new一個Promise時,不會立刻執行異步代碼,而是直接執行了promise.then這個函數,而此時由於self.status的狀態依然是處於pending,因此不會執行resolve或者reject,當同步代碼執行完畢後,執行異步代碼時,更改了狀態爲resolved或者rejected時,此時then方法已經執行完畢了,不會再次執行then的方法,那麼此時咱們該如何處理?
還存在一個問題,就是上面所說的,同一個promise的實例能夠then屢次,成功時會調用全部的成功方法,失敗時會調用全部的失敗方法,那這個又該如何處理呢?
能夠利用咱們前面所說的發佈訂閱的思路來解決,如今咱們看下面代碼。
// ----- 代碼部分
let promise = new Promise((resolve,reject)=>{
setTimeout(()=>{ // 1.此時resolve處於異步
resolve('hello swr')
},3000)
})
promise.then((data)=>{ // 多個then
console.log('success1:' + data)
},(err)=>{
})
promise.then((data)=>{ // 多個then
console.log('success2:' + data)
},(err)=>{
})
複製代碼
// ----- 源碼實現部分
function Promise(executor){
let self = this
self.value = undefined
self.reason = undefined
self.status = 'pending'
self.onResolvedCallbacks = [] // 2.可能new Promise中會有異步的操做,此時咱們把異步操做時,執行的then函數的成功回調,統一保存在該數組中
self.onRejectedCallbacks = [] // 2.可能new Promise中會有異步的操做,此時咱們把異步操做時,執行的then函數的失敗回調,統一保存在該數組中
function resolve(value){
if(self.status === 'pending'){
self.value = value
self.status = 'resolved'
// 4.當調用resolve時,把該數組中存放的成功回調都執行一遍,若是是異步,則會把成功的回調都存到該數組裏了,若是是異步,則沒存到。
self.onResolvedCallbacks.forEach(fn=>fn())
}
}
function reject(reason){
if(self.status === 'pending'){
self.reason = reason
self.status = 'rejected'
// 4.當調用reject時,把該數組中存放的失敗回調都執行一遍,若是是異步,則會把成功的回調都存到該數組裏了,若是是異步,則沒存到。
self.onRejectedCallbacks.forEach(fn=>fn())
}
}
try{
executor(resolve,reject)
}catch(error){
reject(error)
}
}
Promise.prototype.then = function(onFulfilled,onRejected){
let self = this
if(self.status === 'resolved'){
onFulfilled(self.value)
}
if(self.status === 'rejected'){
onRejected(self.reason)
}
// 3.當new Promise中有resolve、reject處於異步中,執行then的時候,狀態爲pending,
if(self.status === 'pending'){
self.onResolvedCallbacks.push(()=>{
onFulfilled(self.value)
}) // 3. 把成功的回調函數,存到該數組中,這樣寫的好處,就是把參數傳進去,不須要未來遍歷onResolvedCallbacks時,再傳參
self.onRejectedCallbacks.push(()=>{
onRejected(self.reason)
}) // 3. 把失敗的回調函數,存到該數組中,這樣寫的好處,就是把參數傳進去,不須要未來遍歷onRejectedCallbacks時,再傳參
}
}
module.exports = Promise
複製代碼
到此爲止,咱們簡版的Promise實現得差很少了,小夥伴們能夠對着代碼敲一下,感覺一下,體會一下。
其實Promise的核心在於鏈式調用,Promise主要是解決2個問題:
首先,好比回調地獄怎麼解決呢?那麼咱們來看下面的代碼,而且改成promise。
// 回調函數
let fs = require('fs')
fs.readFile('./a.txt','utf8',(err,data)=>{ // 往fs.readFile方法傳遞了第三個爲函數的參數
if(err){
console.log(err)
return
}
console.log(data)
})
// 改寫爲Promise
let fs = require('fs')
function read(filePath,encoding){
return new Promise((resolve,reject)=>{
fs.readFile(filePath,encoding,(err,data)=>{
if(err) reject(err)
resolve()
})
})
}
read('./a.txt','utf8').then((data)=>{ // 在這裏則再也不須要傳回調函數進去,而是採用then來達到鏈式調用
console.log(data)
},(err)=>{
console.log(err)
})
// 這樣看好像Promise也沒什麼優點,那麼接下來咱們對比一下
// 假設有3個文件
// - 1.txt 文本內容爲'2.txt'
// - 2.txt 文本內容爲'3.txt'
// - 3.txt 文本內容爲'hello swr'
// 用回調函數
fs.readFile('./1.txt','utf8',(err,data)=>{
fs.readFile(data,'utf8',(err,data)=>{
fs.readFile(data,'utf8',(err,data)=>{
console.log(data) // hello swr
})
})
})
// 用Promise
read('./1.txt','utf8')
.then((data)=>{
// 1.若是一個promise執行完後,返回的仍是一個promise,
// 會把這個promise的執行結果會傳遞給下一次then中
return read(data,'utf8')
})
.then((data)=>{
return read(data,'utf8')
})
.then((data)=>{
// 2.若是在then中返回的不是一個promise,
// 而是一個普通值,會將這個普通值做爲下次then的成功的結果
return data.split('').reverse().join('')
})
.then((data)=>{
console.log(data) // rws olleh
// 3.若是當前then中失敗了,會走下一個then的失敗回調
throw new Error('出錯')
})
.then(null,(err)=>{
console.log(err) // Error:出錯 報錯了
// 4.若是在then中不返回值,雖然沒有顯式返回,
// 可是默認是返回undefined,是屬於普通值,依然會把這個普通值傳到
// 下一個then的成功回調中
})
.then((data)=>{
console.log(data) // undefined
})
複製代碼
從上面能夠看得出,改寫爲Promise的代碼,更好閱讀和維護,從用Promise方式能夠得出結論:
// 若是在then中拋出錯誤,會怎樣呢?
// 情景一,會被下一個then中的失敗回調捕獲
read('./1.txt','utf8')
.then((data)=>{
throw new Error('出錯了')
})
.then(null,(err)=>{
console.log(err) // Error:出錯了 報錯
})
// 情景二,若是沒有被失敗的回調捕獲,拋出錯誤最終會變成異常
read('./1.txt','utf8')
.then((data)=>{
throw new Error('出錯了')
})
// 情景三,若是沒有被失敗的回調捕獲,那麼最終會被catch捕獲到
read('./1.txt','utf8')
.then((data)=>{
throw new Error('出錯了')
})
.then((data)=>{
})
.catch((err)=>{
console.log(err) // Error:出錯了 報錯
})
// 情景四,若是被失敗的回調捕獲了,那麼不會被catch捕獲到
read('./1.txt','utf8')
.then((data)=>{
throw new Error('出錯了')
})
.then(null,(err)=>{
console.log(err) // Error:出錯了 報錯
})
.catch((err)=>{
console.log(err) // 不會執行到這裏
})
複製代碼
jquery的鏈式調用,是經過其內部執行完後return this,返回自身這個對象,達到鏈式調用的目的,那爲何Promise不採用這種方式呢?
咱們能夠看如下代碼,感覺一下。
let promise = new Promise((resolve,reject)=>{
resolve() // 執行resolve,使狀態從pending變爲resolved
})
let promise2 = promise.then(()=>{
throw new Error() // 拋出錯誤
return this // 返回自身
})
// 那麼我在promise2中,調then,那麼它會執行失敗的回調嗎?答案是不會的。
// 由於咱們不可能讓狀態既成功又失敗的
// promise成功了,若是返回this,那不能走向失敗
promise2.then(()=>{
console.log('來到這裏了')
},()=>{
console.log('會來到這裏嗎?')
})
// 此時then中返回自身後,promise2其實就是promise,而咱們想達到
// 的是把當前的then返回後,傳到下一個then中,可是咱們這樣返回this,
// 其實會變得很矛盾,由於狀態已經從pending變爲resolved,不可能又從resolved變成rejected的
// 因此得出結論,返回的必須是一個新的promise,由於promise成功後不能再走失敗
// 只能建立一個新的promise再執行業務邏輯,返回同一個promise的話,就不能既成功又失敗
複製代碼
// ----- 代碼部分
let promise = new Promise((resolve,reject)=>{
resolve()
})
// 2.返回的值爲promise2 爲何這樣規定呢?這是promiseA+規範規定的,咱們要遵循
let promise2 = promise.then((data)=>{
return x // 1.then中的返回值x多是普通值也多是promise,而且傳給下一個then
}).then((data)=>{
console.log(data) // x的值
})
複製代碼
// ----- 源碼實現部分
function Promise(executor){
let self = this
self.value = undefined
self.reason = undefined
self.status = 'pending'
self.onResolvedCallbacks = []
self.onRejectedCallbacks = []
function resolve(value){
if(self.status === 'pending'){
self.value = value
self.status = 'resolved'
self.onResolvedCallbacks.forEach(fn=>fn())
}
}
function reject(reason){
if(self.status === 'pending'){
self.reason = reason
self.status = 'rejected'
self.onRejectedCallbacks.forEach(fn=>fn())
}
}
try{
executor(resolve,reject)
}catch(error){
reject(error)
}
}
Promise.prototype.then = function(onFulfilled,onRejected){
let self = this
let promise2 // 3.上面講promise鏈式調用時,已經說了返回的是一個新的promise對象,那麼咱們聲明一個新的promise
// 4.那麼咱們new一個新的promise,而且把如下代碼放到promise中
let promise2 = new Promise((resolve,reject)=>{
if(self.status === 'resolved'){
// 7.當執行成功回調的時候,可能會出現異常,那麼就把這個異常做爲promise2的錯誤的結果
try{
let x = onFulfilled(self.value) // 6.這裏的x,就是上面then中執行完返回的結果,咱們在這裏聲明一個x用來接收
// 8.根據promiseA+規範,咱們應該提供一個函數來處理promise2
// 我我的的理解是,then中無論是成功回調仍是失敗回調,其返回
// 值,有多是promise,也有多是普通值,也有多是拋出錯誤
// 那麼咱們就須要一個函數來處理這幾種不一樣的狀況
// 這個函數咱們聲明爲resolvePromise吧
resolvePromise(promise2,x,resolve,reject)
// 9. 這裏的promise2就是當前的promise2,x則是執行then中成功回調後返回的結果,若是是成功則調promise2的resolve,失敗則調reject
}catch(e){
reject(e) // 注意:這裏的reject是這個promise2的reject
}
}
if(self.status === 'rejected'){
// 同6-7步
try{
let x = onRejected(self.reason)
// 同8-9
resolvePromise(promise2,x,resolve,reject)
}catch(e){
reject(e)
}
}
if(self.status === 'pending'){
self.onResolvedCallbacks.push(()=>{
// 同6-7步
try{
let x = onFulfilled(self.value)
// 同8-9
resolvePromise(promise2,x,resolve,reject)
}catch(e){
reject(e)
}
})
self.onRejectedCallbacks.push(()=>{
// 同6-7步
try{
let x = onRejected(self.reason)
// 同8-9
resolvePromise(promise2,x,resolve,reject)
}catch(e){
reject(e)
}
})
}
})
return promise2 // 5.在jquery中是return this,可是在promise中,則是返回一個新的promise對象
}
module.exports = Promise
複製代碼
接下來咱們寫一下resolvePromise這個函數,整個Promise最核心的部分就是在這裏
// ----- 代碼部分
let promise = new Promise((resolve,reject)=>{
resolve()
})
let promise2 = promise.then((data)=>{
return x
}).then((data)=>{
console.log(data)
})
// 2.咱們在resolvePromise函數中,在原生狀況下,若是傳參的時候,promise2和x是同一個對象會發生什麼呢?
let promise = new Promise((resolve,reject)=>{
resolve()
})
let promise2 = promise.then(()=>{
return promise2
// 2.1報錯 UnhandledPromiseRejectionWarning: TypeError: Chaining cycle detected for promise #<Promise>
// 報錯的意思是,陷入了死循環,那怎麼理解呢?
// promise2的成功或失敗是要取決於promise中then的返回結果,而返回的倒是promi2本身
// 這樣就陷入死循環了,promise2是依賴於promise的then返回的結果,
// 而then返回的結果是promise2,而then中的promise2,既不是成功也不是失敗,不能本身等於本身
})
// 7.當取一個對象上的屬性,可能存在報異常的狀況,怎麼理解呢?
// 由於這個方法有可能不是本身寫的,可能別人搞惡做劇亂寫的,看如下代碼。
let obj = {}
// 給obj對象定義一個then方法,當咱們去obj對象中調用then方法時
// 就會執行裏面的get,而get則是拋出異常
Object.defineProperty(obj,'then',{
get(){
throw new Error()
}
})
// 10.爲何要用call呢?解決了什麼問題?看一下如下代碼
首先咱們執行
promise.then(()=>{
console.log(this) // 此時this是指向該promise的,對象的方法中this是指向這個對象的
})
可是咱們在下面經過let then = promise.then,來判斷是否promise,是否會異常
當咱們執行then時,裏面的this仍是會指向這個promise嗎?答案是不必定的,
由於此時then,若是在全局下執行,指向的可能就是window了,因此爲了讓this的
指向正確,咱們須要經過
then.call(promise),來把then的this指向promise
複製代碼
// ----- 源碼實現部分
function Promise(executor){
let self = this
self.value = undefined
self.reason = undefined
self.status = 'pending'
self.onResolvedCallbacks = []
self.onRejectedCallbacks = []
function resolve(value){
if(self.status === 'pending'){
self.value = value
self.status = 'resolved'
self.onResolvedCallbacks.forEach(fn=>fn())
}
}
function reject(reason){
if(self.status === 'pending'){
self.reason = reason
self.status = 'rejected'
self.onRejectedCallbacks.forEach(fn=>fn())
}
}
try{
executor(resolve,reject)
}catch(error){
reject(error)
}
}
// 1.聲明一個resolvePromise函數
// 這個函數很是核心,全部的promise都遵循這個規範,全部的promise能夠通用,
/**
*
* @param {*} promise2 then的返回值,返回新的promise
* @param {*} x then中成功函數或者失敗函數的返回值
* @param {*} resolve promise2的resolve
* @param {*} reject promise2的reject
*/
function resolvePromise(promise2,x,resolve,reject){
// 3.從2中咱們能夠得出,本身不能等於本身
// 當promise2和x是同一個對象的時候,則走reject
if(promise2 === x){
return reject(new TypeError('Chaining cycle detected for promise'))
}
// 4.由於then中的返回值能夠爲promise,當x爲對象或者函數,纔有可能返回的是promise
let called
if(x !== null && (typeof x === 'object' || typeof x === 'function')){
// 8.從第7步,能夠看出爲何會存在拋出異常的可能,因此使用try catch處理
try{
// 6.由於當x爲promise的話,是存在then方法的
// 可是咱們取一個對象上的屬性,也有可能出現異常,咱們能夠看一下第7步
let then = x.then
// 9.咱們爲何在這裏用call呢?解決了什麼問題呢?能夠看上面的第10步
// x可能仍是個promise,那麼就讓這個promise執行
// 可是仍是存在一個惡做劇的狀況,就是{then:{}}
// 此時須要新增一個判斷then是否函數
if(typeof === 'function'){
then.call(x,(y)=>{ // y是返回promise後的成功結果
// 一開始咱們在這裏寫的是resolve(y),可是考慮到一點
// 這個y,有可能仍是一個promise,
// 也就是說resolve(new Promise(...))
// 因此涉及到遞歸,咱們把resolve(y)改爲如下
// 12.限制既調resolve,也調reject
if(called) return
called = true
resolvePromise(promise2,y,resolve,reject)
// 這樣的話,代碼會一直遞歸,取到最後一層promise
// 11.這裏有一種狀況,就是不能既調成功也調失敗,只能挑一次,
// 可是咱們前面不是處理過這個狀況了嗎?
// 理論上是這樣的,可是咱們前面也說了,resolvePromise這個函數
// 是全部promise通用的,也能夠是別人寫的promise,若是別人
// 的promise可能既會調resolve也會調reject,那麼就會出問題了,因此咱們接下來要
// 作一下限制,這個咱們寫在第12步
},(err)=>{ // err是返回promise後的失敗結果
if(called) return
called = true
reject(err)
})
}else{
resolve(x) // 若是then不是函數的話,那麼則是普通對象,直接走resolve成功
}
}catch(e){ // 當出現異常則直接走reject失敗
if(called) return
called = true
reject(e)
}
}else{ // 5.x爲一個常量,則是走resolve成功
resolve(x)
}
}
Promise.prototype.then = function(onFulfilled,onRejected){
// onFulfilled、onRejected是可選參數
onFulfilled = typeof onFulfilled === 'function'?onFulfilled:val=>val;
onRejected = typeof onRejected === 'function'?onRejected: err=>{throw err}
let self = this
let promise2
let promise2 = new Promise((resolve,reject)=>{
if(self.status === 'resolved'){
// 13.根據promiseA+規範,onFulfilled或onRejected必須
// 被調用不是當前的上下文,then方法是異步的
setTimeout(()=>{
try{
let x = onFulfilled(self.value)
resolvePromise(promise2,x,resolve,reject)
}catch(e){
reject(e)
}
},0)
}
if(self.status === 'rejected'){
// 同13
setTimeout(()=>{
try{
let x = onRejected(self.reason)
resolvePromise(promise2,x,resolve,reject)
}catch(e){
reject(e)
}
},0)
}
if(self.status === 'pending'){
self.onResolvedCallbacks.push(()=>{
// 同13
setTimeout(()=>{
try{
let x = onFulfilled(self.value)
resolvePromise(promise2,x,resolve,reject)
}catch(e){
reject(e)
}
},0)
})
self.onRejectedCallbacks.push(()=>{
// 同13
setTimeout(()=>{
try{
let x = onRejected(self.reason)
resolvePromise(promise2,x,resolve,reject)
}catch(e){
reject(e)
}
},0)
})
}
})
return promise2
}
// 14.到目前爲止,根據promiseA+規範的代碼寫得差很少了,咱們能夠經過測試代碼來測試咱們是否寫得正確,下面咱們寫一段測試代碼
Promise.defer = Promise.deferred = function(){
let dfd = {}
dfd.promise = new Promise((resolve,reject)=>{
dfd.resolve = resolve
dfd.reject = reject
})
return dfd
}
// 14.接下來咱們要安裝一個插件,npm install promises-aplus-test -g
module.exports = Promise
複製代碼
// 完整代碼 也順便帶你們理順一下
function Promise(executor) {
let self = this;
self.value = undefined; // 成功的值
self.reason = undefined; // 失敗的值
self.status = 'pending'; // 目前promise的狀態pending
self.onResolvedCallbacks = []; // 可能new Promise的時候會存在異步操做,把成功和失敗的回調保存起來
self.onRejectedCallbacks = [];
function resolve(value) { // 把狀態更改成成功
if (self.status === 'pending') { // 只有在pending的狀態才能轉爲成功態
self.value = value;
self.status = 'resolved';
self.onResolvedCallbacks.forEach(fn => fn()); // 把new Promise時異步操做,存在的成功回調保存起來
}
}
function reject(reason) { // 把狀態更改成失敗
if (self.status === 'pending') { // 只有在pending的狀態才能轉爲失敗態
self.reason = reason;
self.status = 'rejected';
self.onRejectedCallbacks.forEach(fn => fn()); // 把new Promise時異步操做,存在的失敗回調保存起來
}
}
try {
// 在new Promise的時候,當即執行的函數,稱爲執行器
executor(resolve, reject);
} catch (e) { // 若是執行executor拋出錯誤,則會走失敗reject
reject(e);
}
}
// 這個函數爲核心,全部的promise都遵循這個規範
// 主要是處理then中返回的值x和promise2的關係
function resolvePromise(promise2,x,resolve,reject){
// 當promise2和then返回的值x爲同一個對象時,變成了本身等本身,會陷入死循環
if(promise2 === x){
return reject(new TypeError('Chaining cycle'));
}
let called;
// x多是一個promise也多是一個普通值
if(x!==null && (typeof x=== 'object' || typeof x === 'function')){
try{
let then = x.then;
if(typeof then === 'function'){
then.call(x,y=>{
if(called) return;
called = true;
resolvePromise(promise2,y,resolve,reject);
},err=>{
if(called) return;
called = true;
reject(err);
});
}else{
resolve(x);
}
}catch(e){
if(called) return;
called = true;
reject(e);
}
}else{
resolve(x);
}
}
// then調用的時候,都是屬於異步,是一個微任務
// 微任務會比宏任務先執行
// onFulfilled爲成功的回調,onRejected爲失敗的回調
Promise.prototype.then = function (onFulfilled, onRejected) {
onFulfilled = typeof onFulfilled === 'function'?onFulfilled:val=>val;
onRejected = typeof onRejected === 'function'?onRejected: err=>{throw err}
let self = this;
let promise2;
// 上面講了,promise和jquery的區別,promise不能單純返回自身,
// 而是每次都是返回一個新的promise,才能夠實現鏈式調用,
// 由於同一個promise的pending resolve reject只能更改一次
promise2 = new Promise((resolve, reject) => {
if (self.status === 'resolved') {
// 爲何要加setTimeout?
// 首先是promiseA+規範要求的
// 其次是你們寫的代碼,有的是同步,有的是異步
// 因此爲了更加統一,就使用爲setTimeout變爲異步了,保持一致性
setTimeout(()=>{
try { // 上面executor雖然使用try catch捕捉錯誤
// 可是在異步中,不必定可以捕捉,因此在這裏
// 用try catch捕捉
let x = onFulfilled(self.value);
// 在then中,返回值多是一個promise,因此
// 須要resolvePromise對返回值進行判斷
resolvePromise(promise2,x,resolve,reject);
} catch (e) {
reject(e);
}
},0)
}
if (self.status === 'rejected') {
setTimeout(()=>{
try {
let x = onRejected(self.reason);
resolvePromise(promise2,x,resolve,reject);
} catch (e) {
reject(e);
}
},0)
}
if (self.status === 'pending') {
self.onResolvedCallbacks.push(() => {
setTimeout(()=>{
try {
let x = onFulfilled(self.value);
resolvePromise(promise2,x,resolve,reject);
} catch (e) {
reject(e);
}
},0)
});
self.onRejectedCallbacks.push(() => {
setTimeout(()=>{
try {
let x = onRejected(self.reason);
resolvePromise(promise2,x,resolve,reject);
} catch (e) {
reject(e);
}
},0)
});
}
});
return promise2
}
Promise.defer = Promise.deferred = function(){
let dfd = {};
dfd.promise = new Promise((resolve,reject)=>{
dfd.resolve = resolve;
dfd.reject = reject;
})
return dfd;
}
module.exports = Promise;
複製代碼
執行promises-aplus-tests promise.js
複製代碼
到此爲止,咱們已經寫了一個符合promiseA+規範的promise了,你們能夠好好多看幾回。
Promise.reject = function(reason){
return new Promise((resolve,reject)=>{
reject(reason);
})
}
Promise.resolve = function(value){
return new Promise((resolve,reject)=>{
resolve(value);
})
}
Promise.prototype.catch = function(onRejected){
return this.then(null,onRejected);
};
Promise.all = function(promises){
return new Promise((resolve,reject)=>{
let arr = [];
let i = 0;
function processData(index,data){
arr[index] = data;
if(++i == promises.length){
resolve(arr);
}
}
for(let i = 0;i<promises.length;i++){
promises[i].then(data=>{
processData(i,data);
},reject);
}
})
}
Promise.race = function(promises){
return new Promise((resolve,reject)=>{
for(let i = 0;i<promises.length;i++){
promises[i].then(resolve,reject);
}
})
}
複製代碼
// 原生的Promise.resolve使用
Promise.resolve('hello swr').then((data)=>{ // 直接把成功的值傳遞給下一個then
console.log(data) // hello swr
})
// 那麼Promise.resolve內部是怎麼實現的呢?
Promise.resolve = function(value){
return new Promise((resolve,reject)=>{ // 在內部new一個Promise對象
resolve(value)
})
}
// 同理,Promise.reject內部也是相似實現的
Promise.reject = function(reason){
return new Promise((resolve,reject)=>{
reject(reason)
})
}
複製代碼
// 原生Promise的catch使用
Promise.reject('hello swr').catch((e)=>{
console.log(e) // hello swr
})
// 上面這段代碼至關於下面這段代碼
Promise.reject('hello swr').then(null,(e)=>{ // then裏直接走了失敗的回調
console.log(e) // hello swr
})
// 內部實現
Promise.prototype.catch = function(onRejected){
return this.then(null,onRejected) // 至關於then裏的成功回調只傳個null
}
複製代碼
// 原生Promise.all的使用
// 假設1.txt內容爲hello 2.txt內容爲swr
let fs = require('fs')
function read(filePath,encoding){
return new Promise((resolve,reject)=>{
fs.readFile(filePath,encoding,(err,data)=>{
if(err) reject(err)
resolve(data)
})
})
}
Promise.all([read('./1.txt','utf8'),read('./2.txt','utf8')]).then((data)=>{
console.log(data) // 所有讀取成功後返回 ['hello','swr']
// 須要注意的是,當其中某個失敗的話,則會走失敗的回調函數
})
// 內部實現
Promise.all = function(promises){ // promises 是一個數組
return new Promise((resolve,reject)=>{
let arr = []
let i = 0
function processData(index,data){
arr[index] = data
// 5.咱們能用arr.length === promises.length來判斷請求是否所有完成嗎?
// 答案是不行的,假設arr[2] = 'hello swr'
// 那麼打印這個arr,將是[empty × 2, "hello swr"],
// 此時數組長度也是爲3,而數組arr[0] arr[1]則爲空
// 那麼換成如下的辦法
if(++i === promises.length){ // 6.利用i自增來判斷是否都成功執行
resolve(arr) // 此時arr 爲['hello','swr']
}
}
for(let i = 0;i < promises.length;i++){ // 1.在此處遍歷執行
promises[i].then((data)=>{ // 2.data是成功後返回的結果
processData(i,data) // 4.由於Promise.all最終返回的是一個數組成員按照順序排序的數組
// 並且異步執行,返回並不必定按照順序
// 因此須要傳當前的i
},reject) // 3.若是其中有一個失敗的話,則調用reject
}
})
}
複製代碼
// 原生Promise.race的使用
// 一個成功就走成功的回調,一個失敗就走失敗的回調
Promise.race([read('./1.txt','utf8'),read('./2.txt','utf8')]).then((data)=>{
console.log(data) // 可能返回 'hello' 也可能返回 'swr' 看哪一個返回快就用哪一個做爲結果
})
// 內部實現
Promise.race = function(promises){ // promises 是一個數組
return new Promise((resolve,reject)=>{
for(let i = 0;i < promises.length;i++){
promises[i].then(resolve,reject) // 和上面Promise.all有點相似
}
})
}
複製代碼
這個語法糖能夠簡化一些操做,好比
let fs = require('fs')
// 寫法一:
function read(filePath,encoding){
// 這裏的new Promise依然是傳遞了一個executor回調函數
// 咱們該怎樣減小回調函數嵌套呢?
return new Promise((resolve,reject)=>{
fs.readFile(filePath,encoding,(err,data)=>{
if(err) reject(err)
resolve(data)
})
})
}
// 寫法二:
// 這樣的寫法減小了一層回調函數的嵌套
function read(filePath,encoding){
let dfd = Promise.defer()
fs.readFile(filePath,encoding,(err,data)=>{
if(err) dfd.reject(err)
dfd.resolve(data)
})
return dfd.promise
}
read('./1.txt','utf8').then((data)=>{
console.log(data)
})
複製代碼
結尾:第一次寫,都是想到哪寫到哪,請你們多多諒解~ 也但願對你們有所幫助,promise的源碼實現,我最大的收穫並非怎麼實現promise,而是編程思惟,你們能夠多多往深裏想想,也但願你們能夠和我進行交流,共同進步