javascript 進階之 - Promise
引言
promise 主要解決了回撥地域, 也就是巢狀太深的 callback, 而採用鏈式方式. 如:
// 普通方式
$.get({
url:'url',
success:function(){
$.get({
url:'url2',
success:function(){
$.get({
url:'url3',
success:function(){
// ...
}
})
}
})
}
})
// promise , 假如 $.get 支援 promise 方式
$.get({
url:'url1'
})
.then(res=>{
return $.get({
url :'url2'
})
})
.then(res=>{
console.log('ok')
})
.catch(()=>{
console.log('error')
})
Promise 物件
- 構造 Promise 例項時, 引數為一個函式
var p1 = new Promise(function(resolve,reject){
console.log('promise start')
})
// Promise 內部類似
function Promise(fn){
var resolve = function(){}
var reject = function(){}
fn(resolve,reject);
return this;
}
- then, 收集 resolve 後要執行的回撥 ; catch , 收集 reject 後要執行的回撥
var p1 = new Promise(function(resolve,reject){
console.log('promise start')
})
p1.then(function(){
console.log(1)
})
.then(function(){
console.log(2)
})
// Promise 內部類似
function Promise(fn){
var resolve = ()=>{
this.thenList.forEach((fn)=>{
fn();
})
}
var reject = ()=>{
this.catchList.forEach((fn)=>{
fn();
})
}
this.thenList = [] ;
this.catchList = [];
fn(resolve,reject);
this.then = function(callback){
this.thenList.push(callback);
return this;
}
this.catch = function(callback){
this.catchList.push(callback) ;
return this;
}
return this;
}
實踐發現 , 並不能列印出 1 , 因為 resolve 的執行, 是早於 then 的呼叫, 這個時候的 thenList 還是個空陣列. 所以修改一下, 先讓 then 執行. 也就是利用事件迴圈的原理.
function Promise(fn){
var resolve = ()=>{
setTimeout(()=>{
this.thenList.forEach((fn) => {
fn();
})
},0)
};
var reject = ()=>{
setTimeout(()=>{
this.catchList.forEach((fn) => {
fn();
})
},0)
};
// 其他不變
}
- 而還有一個要注意的點就是 , then 的時候, 是可以返回一個新的 Promise 物件, 打斷當前鏈條的. 而返回其他非 Promise 對 then 的鏈式無影響.
如下, 並未在返回新的 Promise 未打斷原 Promise 的 then 鏈式:
var p1 = new Promise(function (resolve, reject) {
console.log('promise start')
resolve();
})
.then(function () {
console.log(1)
return new Promise(function (resolve, reject) {
reject();
})
})
.then(function () {
console.log(2)
})
.catch(function () {
console.log('error')
})
// 應該列印 : promise start , 1 , error
可以通過對每次的 then 和 catch 做返回判斷, 如果返回的是 Promise 物件, 則停止之前的 then 和 catch 執行, 將剩餘未執行的拼在返回的 Promise 的物件的原有 thenList 和 catchList 後面 .
這裡順便也加上引數和狀態.
function Promise(fn) {
this.status = 'Pending';
var resolve = (...args) => {
this.status = 'Resolved';
setTimeout(() => {
for (var i = 0; i < this.thenList.length; i++) {
var result = this.thenList[i](...args);
if (result instanceof Promise) {
result.thenList = [...result.thenList, ...this.thenList.slice(i + 1)]
result.catchList = [...result.catchList, ...this.catchList]
break;
}
}
})
}
var reject = (...args) => {
this.status = 'Rejected';
setTimeout(() => {
for (var i = 0; i < this.catchList.length; i++) {
var result = this.catchList[i](...args);
if (result instanceof Promise) {
result.thenList = [...result.thenList, ...this.thenList]
result.catchList = [...result.catchList, ...this.catchList.slice(i + 1)]
break;
}
}
})
}
this.thenList = [];
this.catchList = [];
fn(resolve, reject);
this.then = function (callback) {
this.thenList.push(callback);
return this;
}
this.catch = function (callback) {
this.catchList.push(callback);
return this;
}
return this;
}
需要特別說明的是: 這裡雖然借用 setTimeout 實現 , 但 promise 和 setTimeout 在事件迴圈中的表現還是有差異的 , promise 是生成微任務 jobs, 而 setTimeout 則是生成巨集任務 , 也就是 task , 每次的 task 是一個任務 , 一個完整的任務包括 jobs .
async / await
async 和 await 只是 promise 的語法糖, 其核心還是 promise .
既然是糖, 吃多了對牙口不好.
- await 替代 promise 的 then 的寫法
使用 await 可以省略 then 的鏈式寫法 , 但是 await 必須在 async “裝飾” 的函式體內. 也就是 await 不能寫在全域性作用域內.
// 常規方式新建 promise 物件
var p1 = new Promise((resolve,reject)=>{
setTimeout(()=>{ resolve('hello') },5000)
})
// 常規 promise then 呼叫
p1.then(result =>{ console.log('normal',result) })
// await 方式全域性作用域呼叫
// var result = await p1; // 報錯 Uncaught SyntaxError: await is only valid in async function
// await 正常呼叫 => 在一個 async 修飾的函式內
async function test(){
console.log('async start')
var result = await p1;
console.log('await',result)
}
test();
console.log('end');
// 列印 async start , end , normal hello , await hello
從上述例子, 可以得出以下幾點:
1. await 等的是一個 promise 的 resolve 的結果
2. await 只能在一個 async 修飾的函式體內
3. await 時, 不會堵塞 await 所在的 async 的函式體之外的 js 執行
所以通俗 await 做了哪些事
// 常規寫法
var result ;
p1.then(res=>{
result = res
console.log(result);
})
// await 寫法
var result = await p1;
console.log(result);
還有就是 await 可以不只是等 promise , 也可以等一個同步函式
function sayHi(){
return 'hi';
}
async function test(){
var result = await sayHi();
console.log(result);
}
test();
console.log('end');
// 列印結果: end , hi
可以看出, 雖然 await 的是一個同步執行, 直接返回了 hi , 但是卻是在 end 後面輸出; 由此可以大膽猜測 , await 的作用就是把它後面的程式碼都放到了 promise 的 then 中
- async 修飾一個函式返回 promise 物件
// 常規建立 promise 物件
var p1 = new Promise((resolve,reject)=>{
resolve('hello');
})
// async 建立 promise 物件
// 1. 無返回 , 則是 resolve(undefined)
var p2 = (async function b(){
})();
// 2. 有返回, 則是 resolve(返回值)
var p3 = (async function c(){
return 'hi';
})();
// 呼叫
p1.then(res=>{console.log(res)});
p2.then(res=>{console.log(res)});
p3.then(res=>{console.log(res)});
console.log('end');
// 列印 : end , hello , undefined , hi
注意:
1. async 用來 “修飾” 函式, 但該函式並不是 promise , 而是該函式執行後, 返回一個 promise 物件.
2. async “修飾” 的函式有返回值, 則作為 resolve 的引數返回, 無返回值或者無返回, 則 undefined 作為 resolve 的結果返回.
async 和 promise 的 resolve 的一點差別
// 常規 promise 的延時返回
var p1 = new Promise((resolve,reject)=>{
setTimeout(()=>{ resolve('hi') },5000);
})
// await 如果照常規寫法返回
var p2 = (async function b(){
setTimeout(()=>{ return ('hi')},5000)
})();
p1.then(res=>{ console.log(res) }); // 5 秒後列印 hi
p2.then(res=>{ console.log(res) }); // undefined
// 列印結果: undefined , 5秒後列印 hi
// 那麼怎麼實現 async 中的 延遲效果? 也就是說 d 函式需要等 (await) 5秒, 再返回出去
var p3 = (async function d(){
await wait(5000);
return 'hi';
})();
function wait(s){
return new Promise((resolve,reject)=>{
setTimeout(()=>{ resolve() },s)
})
}
p3.then(res=>{ console.log(res) });
那麼, 從上面可以看出什麼端倪?
1. async “修飾” 的函式, 也還是一個同步函式, 也就是它急切地完成, 急切地需要一個結果, 急切地包裝成一個 promise 物件並返回. 所有 p2 他不會等非同步的定時器走完, 返回 ‘hi’ , 才結束一個函式. 直接跳過非同步事件, 走完整個函式, 找到返回值包裝返回.
2. async 並不能代替 new Promise 做事情, 主要作用是用來 , 框定一部分非同步程式碼 (await) 邏輯 ,使得其按 promise 的 then 方式順序執行, 又不至於影響到範圍之外的同步程式碼的執行.
await 和 async 總結
- await 只能存在 async 修飾的函式體內
- await 後面的程式碼, 都將等待 await 的 promise 的 resolve , 也就是堵塞的.
- async 單獨用處不大, 主要用來配合 await .
目前綜合來看 , await 和 async 的組合的主要作用, 是解決了 then 的存在和 then 的冗長的鏈式呼叫. 然後通過區域性變數接收 promise 的 then 的結果, 方便後面的呼叫, 避免了 多個 then 需要傳參的尷尬.
await 的錯誤捕獲 和 Promise.all 等後面專門總結一下.
總結
以上 Promise 模擬實現, 並非 js 內建實現的 Promise , 只是用簡單的程式碼模擬 , 強調 Promise 其中的幾個特性.
- Promise 是一個函式.
- Promise 的引數是一個函式 ( fn ), 且在生成 Promise 例項時, 這個函式會立即執行.
- Promise 的 then 和 catch 的引數也是函式, 在生成 Promise 例項時, 並不會直接執行函式 , 但是會掛載在 Promise 例項上 .
- Promise 傳入的 fn 執行 resolve 或 reject 時 , 這時候的 Promise 例項必然已經是初始化完成, 即 then 和 catch 都已經掛載完畢.
- 第 4 條 , 也就是說明 resolve 和 reject 觸發 的 then 和 catch 在同步程式碼執行之後 . 而且實際上是在 setTimeout 之前.
- 在 then 和 catch 中可以返回一個新的 Promise , 打斷之前 Promise 鏈.
研究新的事物, 可以從兩個方面出發:
- 用舊有的已經掌握的知識來推導新知識, 以及建立聯絡
- 控制變數法, 每次只研究其中一小部分, 其他保持不變.
相關參考
相關文章
- JavaScript進階之繼承JavaScript繼承
- JavaScript進階之原型鏈JavaScript原型
- JavaScript進階之(一) this指標JavaScript指標
- Javascript基礎之-PromiseJavaScriptPromise
- JavaScript之淺析PromiseJavaScriptPromise
- js進階--Promise-10JSPromise
- Promise進階——如何實現一個Promise庫Promise
- JavaScript進階JavaScript
- JavaScript進階之函式柯里化JavaScript函式
- 前端進階之 Javascript 抽象語法樹前端JavaScript抽象語法樹
- 深入理解Javascript之PromiseJavaScriptPromise
- JavaScript進階之模擬new Object()過程JavaScriptObject
- JavaScript進階之模擬new Object過程JavaScriptObject
- JavaScript進階之模擬call,apply和bindJavaScriptAPP
- 前端入門13-JavaScript進階之原型前端JavaScript原型
- [ ES6 ] 進階篇(一) —— PromisePromise
- 前端入門15-JavaScript進階之原型鏈前端JavaScript原型
- 前端入門19-JavaScript進階之閉包前端JavaScript
- javascript非同步解決方案之promiseJavaScript非同步Promise
- Javascript — PromiseJavaScriptPromise
- Promise in JavascriptPromiseJavaScript
- 前端入門16-JavaScript進階之EC和VO前端JavaScript
- [面試] 考驗你對 Promise 的熟度之進階應用題面試Promise
- JavaScript進階教程日記JavaScript
- 【2019 前端進階之路】站住,你這個Promise!前端Promise
- Javascript Promise用法JavaScriptPromise
- JavaScript Promise物件JavaScriptPromise物件
- JavaScript Promise 物件JavaScriptPromise物件
- Web前端進階之JavaScript模組化程式設計知識Web前端JavaScript程式設計
- 【進階1-4期】JavaScript深入之帶你走進記憶體機制JavaScript記憶體
- JavaScript Promise(基礎)JavaScriptPromise
- JavaScript Promise 詳解JavaScriptPromise
- Django 進階之 celeryDjango
- 前端進階之困前端
- JavaScript正規表示式進階指南JavaScript
- 【JavaScript高階進階】JavaScript變數/函式提升的細節總結JavaScript變數函式
- 高階前端的進階——CSS之flex前端CSSFlex
- 進擊的 JavaScript(六) 之 thisJavaScript