what is promise
MDN 裏面有詳細的簡介
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Guide/Using_promises
1.同步和異步
我們知道js的執行環境是單線程的。所謂的單線程,是指js引擎中負責解釋和執行js代碼的線程只有一個,也就是一次只能完成一項任務,這個任務執行完才能開始下一個,它會阻止其他的任務,就是js代碼自上而下的執行,只能等待上面的代碼全部執行完事才能開始它的下面的代碼的執行。這個任務可稱爲主線程。但是實際上還有其他的線程,如事件觸發線程、ajax請求線程等。這也就引發了同步和異步的問題哦。
1.1 同步
同步模式,就是上面所說的單線程模式, 一次只能執行 一個任務,函數調用後需要等到函數執行結束,返回執行的結果,才能執行下一個任務。如果這個任務執行的時間較長,就會導致 線程阻塞
例1.1
const x = true
while(x);
console.log("don not carry me !!!")
上面的例子就是同步模式,其中的while是一個死循環,它會阻塞進程,因此console.log是在控制檯打印不出來的。
同步模式比較簡單,也比較容易編寫。但問題也顯而易見,如果請求的時間比較長,而阻塞了後面代碼的執行,體驗是很不好的。因此對於一些耗時的操作,異步模式則是更好的選擇。
1.2 異步
異步模式,即與同步模式相反,可以一起執行 多個任務,函數調用後不會立即返回執行的結果,如果任務A需要等待,可先執行任務B,等到任務A結果返回後再繼續回調。
最常見的模式就是定時器了,我們來看看下面的例子
setTimeout(function(){
console.log('taskA,asynchronous')
},0)
console.log('taskB,synchronous')
// while(x);
控制檯的輸出爲
taskB,synchronous
taskA,asynchronous
我們可以看到,定時器延時的時間明明爲0,但taskA還是晚於taskB執行。這是爲什麼呢???由於定時器是異步的, 異步任務會在當前腳本的所有同步任務執行完纔會執行。 如果同步代碼中含有死循環,即將上面的while的註釋去掉,那麼這個異步任務就不會執行,因爲同步任務阻塞了進程。
1.3回調函數
說到異步,就一定要說一下回調函數了。上面的例子中,setTimeout裏面的function便是回調函數。可以簡單理解爲:(執行完)回(來)調(用)的函數。
回調函數是一段可執行的代碼段,它以參數的形式傳遞給其他代碼,在合適的時間執行這段(回調函數)的代碼。
回調函數不僅可以用於異步調用,一般同步的場景也可以用回調。在同步調用下,回調函數一般是最後執行的。而在異步調用下,可能一段時間後執行或者步執行。
//同步回調
var fun1 = function(callback){
//do something
//這個地方一定要加分號否則會報錯
console.log('before callback');
(callback && typeof(callback) === 'function') && callback()
console.log('after callback')
}
var fun2 = function(param){
// do something
var start = new Date()
while((new Date() - start) < 3000){
}
console.log('i am callback')
}
fun1(fun2)
輸出結果爲:
before callback
i am callback
after callback
由於是同步回調,會阻塞後面的代碼,如果fun2是個死循環,後面的代碼就不會再執行了。setTimeout是常見的異步回調,另外的一個異步回調即ajax請求。
2.爲什麼要使用Promise
說完以上的基本概念,我們就可以繼續學習Promise了
上面提到,Promise對象是用於異步操作的,既然我們可以使用異步回調函數來進行異步操作,爲什麼還要引入一個promise新概念,還要花時間學習它呢,我們可以看看Promise的過人之處。利用Promise改寫異步回調。
function sendRequest(url,param){
return new Promise(function(resolve,reject){
request(url,param,resolve,reject)
})
}
sendRequest('test.html','').then(function(data){
//異步操作成功後的回調
console.log('請求成功了,這是返回的數據:',data)
},function(error) {
//異步操作失敗後的回調
console.log('請求失敗了,這是返回的數據:',error)
})
這麼一看,並沒有什麼大的區別,還比上面的異步回調複雜,需要先新建Promise再定義其回調。其實,Promise的真正強大之處在於它的多重鏈式調用,可以避免層層嵌套回調。如果我們在第一次ajax請求後,還要用它返回的結果再次請求呢???
request('test.html','',function(data1){
console.log('這是第一次請求成功,這是返回的數據:',data1)
request('test.html','data1',function(data2){
console.log('這是第二次請求成功,這是返回的數據:',data2)
request('test.html','data2',function(data3){
console.log('這是第三次請求成功,這是返回的數據:',data3)
},function(error3){
console.log('這是第三次請求失敗,這是返回的數據:',error3)
})
},function(error2){
console.log('這是第二次請求失敗,這是返回的數據:',error2)
})
},function(error1){
console.log('這是第一次請求失敗,這是返回的數據:',error1)
})
上面的代碼出現了多層回調嵌套,有種暈頭轉向的感覺,編程體驗十分不好,而使用Promise,我們就可以使用then進行鏈式回調,將異步操作以同步方式操作的流程表示出來。
sendRequest('test.html','').then(function(data1){
console.log('這是第一次請求成功,這是返回的數據:',data1)
return sendRequest('test2.html',data1)
}).then(function(data2){
console.log('這是第二次請求成功,這是返回的數據:',data2)
return sendRequest('test3.html',data2)
}).then(function(data3){
console.log('這是第三次請求成功,這是返回的數據:',data3)
}).catch(function(error){
//使用catch捕獲前面的錯誤
console.log('這是第一次請求失敗,這是失敗的信息:',error)
})
是不是感覺到清晰明顯一些,下面我們開始正式進入Promise的學習。
3 Promise的基本用法
3.1 基本用法
上面我們認識了promise長什麼樣,但對它用到的resolve,rejected,then,catch想必還不瞭解。
Promise對象代表一個未完成、但是預計將來會完成的操作。
它有以下三種狀態:
- pending:初始值,不是fulfilled,也不是rejected
- fulfilled:代表操作成功
- rejected:代表操作失敗
promise有兩種狀態改變的方式,既可以從pending轉變爲 fulfilled,也可以從pending轉變爲rejected。一旦狀態改變,就不變了。當狀態發生變化,promise.then綁定的函數就會被調用。
promise一旦新建就會立即執行,無法取消。這是它的缺點之一。
var promise = new Promise(function(resolve,reject){
if(/*異步操作成功*/){
resolve(data)
}else{
reject(error)
}
})
類似構建對象,我們使用new來構建一個 promise。 promise接受一個函數作爲參數,該函數的兩個參數分別是resolve和reject。這兩個函數就是回調函數,由js引擎提供。
resolve函數的作用:在異步操作成功時的調用,並將異步的操作結果,作爲參數傳遞出去;
reject函數的作用:在異步操作失敗時的調用,並將異步操作報出的錯誤,作爲參數傳遞出去;
promise實例生成以後,可以用then方法指定resolved狀態和reject狀態的回調函數。
promise.then(onFulfilled,onRejected);
promise.then(function(data){
// do something when success
},function(error){
// do something when fail
})
then方法會返回一個Promise。then就是定義resolve和reject函數的,其resolve參數相當於:
function resolveFun(data){
// data爲promise傳出的值
}
而新建Promise中的’resolve(data)’,則相當於執行resolveFun函數。
Promise新建後就會立即執行。而then方法中指定的回調函數,將在當前腳本所有同步任務執行完纔會執行。
var promise = new Promise(function(resolve,reject){
console.log('before resolved')
resolve()
console.log('after resolved')
})
promise.then(function(){
console.log('resolved')
})
console.log('outer')
輸出順序:
before resolved
after resolved
outer
resolved
3.2基本API
.then()
語法:promise.prototype.then(onFufilled,onRejected)
對promise添加onFufilled,onRejected回調,並返回的是一個新的promise實例,且返回值將作爲參數傳入這個新的promise的resolve函數。
.catch()
語法:promise.prototype.then(onRejected)
該方法是.then(undefined,onRejected)的別名,用於指定發生錯誤時的回調函數。
promise.then(function(data){
console.log(success)
}).catch(function(error){
console.log(error)
})
//等同於
promise.then(function(data){
console.log(success)
}).catch(undefined,function(error){
console.log(error)
})
var promise = new Promise(function(resolve,reject){
throw new Error('test')
})
//等同於
var promise = new Promise(function(resolve,reject){
reject(new Error('test'))
})
//用catch捕獲
promise.catch(function(error){
console.log(error)
})
輸出:test
reject方法的作用就等同於拋錯。
promise對象的錯誤,會一直向後傳遞,直到被捕獲。即錯誤總會被下一個catch所捕獲。then方法指定的回調函數,若拋出錯誤,也會被下一個catch捕獲。catch中也能拋錯,則需要後面的catch來捕獲。
.all()
語法:Promise.all(iterable)
該方法用於將多個Promise實例,包裝成一個新的Promise實例
var p = Promise.all([p1,p2,p3])
Promise.all方法接受一個數組做參數,數組中的對象(p1,p2,p3)均爲promise實例,它的狀態將由這3個promise實例來決定。
- 當p1,p2,p3狀態都變成Fulfilled,p的狀態纔會變成Fulfilled,並將三個promise返回的結果,按參數的順序(而不是resolve()的順序)存入數組,傳給p的回調函數
var p1 = new Promise(function(resolve,reject){
setTimeout(resolve,3000,'first')
})
var p2 = new Promise(function(resolve,reject){
resolve('second')
})
var p3 = new Promise(function(resolve,reject){
resolve(resolve,1000,'third')
})
Promise.all([p1,p2,p3]).then(function(values){
console.log(values)
})
約3秒後
[‘first’,‘second’,‘third’]
- 當p1,p2,p3其中之一狀態變爲rejected,p的狀態也會變成rejected,並把第一個被reject的promise的返回值,傳給p的回調函數。
var p1 = new Promise(function(resolve,reject){
setTimeout(resolve,1000,'first')
})
var p2 = new Promise(function(resolve,reject){
resolve(resolve,2000,'second')
})
var p3 = new Promise(function(resolve,reject){
reject('third')
})
Promise.all([p1,p2,p3]).then(function(values){
console.log(values)
}).catch(function(error){
console.log(error)
})
輸出:
three
多個promise是同時開始執行的,並行執行的,而不是順序執行的。
function timePromisefy(delay){
return new Promise(function(resolve){
setTimeout(function(){
resolve(delay);
},delay)
})
}
var startDate = Date.now()
Promise.all([
timePromisefy(1),
timePromisefy(32),
timePromisefy(64),
timePromisefy(128),
]).then(function(values){
console.log(Date.now() - startDate +'ms')
})
最後輸出爲133ms //會大於128,但不是所有的時間總和
.resolve()
Promise.resolve(value)
Promise.resolve(promise)
Promise.resolve(thenable)
它可以看作是new Promise()的快捷方式
Promise.resolve('success')
//等同於
new Promise(function(resolve){
resolve('success')
})
.reject()
Promise.reject(reason)
它可以看作是new Promise()的快捷方式
Promise.resolve('success')
//等同於
new Promise(function(resolve,reject){
reject(new Error('error'))
})
4.總結
創建promise的流程:
1.使用new Promise(fn)或者它的快捷方式Promise.resolve()、Promise.reject(),返回一個promise對象
2.在fn中指定異步的處理
處理結果正常,調用resolve
處理結果錯誤,調用reject
最後希望大家能夠有所收穫!!!