Promise 是一個因應非同步操作的需求所產生的語法
可以參考MDN有關Promise的說明
https://developer.mozilla.org/zh-TW/docs/Web/JavaScript/Reference/Global_Objects/Promise
在MDN產的介紹提到
Promise 物件代表一個即將完成、或失敗的非同步操作,以及它所產生的值。
Promise是處理非同步的操作,
在原本處理非同步操作的作法,以jQuery為例
<div id="result"></div>
<script>
$("#result").load("ajax/test.html", () => {
console.log( "Load was performed." );
});
</script>
由以上可看出
在執行完 $("#result").load()
的動作後,成功的話接著會執行
這個程式() => { console.log( "Load was performed." ); }
也就是所謂的callback的處理
如果有些操作需要等待資料載入完成才會做下一個動作時
就會形成巢狀式的等待, 如以下程式
<script>
$("#div1").load("test1.html", () => {
console.log( "Load1 was performed." );
$("#div2").load("test2.html", () => {
console.log( "Load2 was performed." );
$("#div3").load("test3.html", () => {
console.log( "Load3 was performed." );
});
});
});
</script>
或是形成以下的程式碼
<script>
$("#div1").load("test1.html", () => {
console.log( "Load1 was performed." );
act2();
});
function act2(){
$("#div2").load("test2.html", () => {
console.log( "Load2 was performed." );
act3();
});
}
function act3(){
$("#div3").load("test3.html", () => {
console.log( "Load3 was performed." );
});
}
</script>
舉以上的例子,在於理解原本的作法
在改成Promise後,產生什麼樣的變化
基本上Promise要處理的就是 執行的成功處理,失敗處理,以及往下傳遞的處理
透過實際的程式練習,就可以找出理解的盲點及解決的方法
let p1 = new Promise( (resolve, reject) => {
if(Math.random()<0.5){
console.log("OK1 Success!");
resolve("Success!");
} else {
console.log("ERR1 Error!");
reject ("Error!");
}
}).then(
(value) => {
console.log("OK2 "+ value);
return value;
},
(reason) => {
console.log("ERR2 "+reason);
throw reason;
}
).then(
(value) => {
console.log("OK3 "+ value);
},
(reason) => {
console.log("ERR3 "+reason);
}
);
Promise(resolve, reject)
: 成功 resolve(value),失敗 reject(reason)
then(value, reason) : 成功 return value,失敗 throw reason
在 Promise 區段 執行完,若要往下執行,利用 成功 resolve(value),失敗 reject(reason)
resolve(value)就相當於 callback,往下執行 then(),
同時按照then()的順序執行
let p2 = new Promise( (resolve, reject) => {
$("#div1").load("test1.html", () => {
console.log( "Load1 was performed." );
resolve("Success!");
});
}).then(
(value) => {
setTimeout(() => {
$("#div2").load("test2.html", () => {
console.log( "Load22 was performed.");
});
}, 2000);
console.log( "Load21 was performed.");
return value;
}
).then(
(value) => {
$("#div3").load("test3.html", () => {
console.log( "Load3 was performed.");
});
}
);
由上面的程式可以發現執行的順序是
Load1 -> Load21 -> Load3 -> Load22
在 Load22 中,加了一個 Timeout 的處理,會延遲2秒執行
在此有一個盲點就是 程序上是 Load1 -> Load21 -> Load3 的順序
並不會等待 Timeout 處理完才走到 下一步
如果想再等 Timeout 處理完再走到下一步,就要使用另一個用法了
就是 async 與 await
Promise可以看作是控制執行的流程
//-----------------
接著就來研究一下 async 與 await
為了解決 需要等待上一個動作完成,才能往下執行的需求
避免 巢狀callback的問題
就要透過 async 與 await 的語法來解決了
有關async 與 await的說明,可以參考MDN的介紹
async function
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function
await
https://developer.mozilla.org/zh-TW/docs/Web/JavaScript/Reference/Operators/await
先來看一下範例
以下是沒有 await 的狀況
function resolveAfter2Seconds() {
return new Promise((resolve) => {
setTimeout(() => {
resolve('resolved');
}, 2000);
});
}
async function asyncCall() {
console.log('calling');
const result = resolveAfter2Seconds();
console.log(result);
// Expected output: "resolved"
}
asyncCall();
執行結果
k1: calling
k3: [object Promise]
k2: timeout
以下是有 await 的狀況
function resolveAfter2Seconds() {
return new Promise((resolve) => {
setTimeout(() => {
resolve('resolved');
}, 2000);
});
}
async function asyncCall() {
console.log('calling');
const result = await resolveAfter2Seconds();
console.log(result);
// Expected output: "resolved"
}
asyncCall();
執行結果
k1: calling
k2: timeout
k3: resolved
在Promise中加上then()的狀況
function resolveAfter2Seconds() {
return new Promise((resolve) => {
setTimeout(() => {
console.log("k21: timeout");
resolve('resolved');
}, 2000);
}).then(
(value) => {
console.log("k22: then "+value);
return 123;
}
);
}
async function asyncCall() {
console.log('k1: calling');
const result = await resolveAfter2Seconds();
console.log("k3: "+result);
}
asyncCall();
執行結果
k1: calling
k21: timeout
k22: then resolved
k3: 123
整理如下
function resolveAfter2Seconds() {
return new Promise((resolve) => {
setTimeout(() => {
resolve('resolved');
}, 2000);
}).then(
(value) => {
return 123;
}
);
}
async function asyncCall() {
const result = await resolveAfter2Seconds();
}
asyncCall();
async asyncCall()
-> await resolveAfter2Seconds()
-> return new Promise( (resolve) => { resolve('resolved'); })
.then( (value) => { return 123; });
-> result
再整理一下流程
async asyncCall()
-> await resolveAfter2Seconds()
-> return Promise()
-> resolve(value)
-> result = value;
Promise, async, await 的整合操作的順序
1. 建立 async 的 function
2. 設定 要 await 的 function 及回傳值
3. 透過 await 的 function 的return new Promise()的方式 執行要 await 的項目
4. 透過 Promise 的 resolve(value); 傳回值 value 給then()
5. 透過 then() 的 return value 傳回值 value 給變數 result
簡單的來說就是
執行 async 及 await,等待 Promise 的 resolve 的回傳值
沒有await的, 即時回傳 function 的return 值
有 await的,會等待 Promise 的 resolve 的回傳值
熟練以上的基本步驟後,再應用到各種不同的狀況
以下是一些不同的寫法範例
async function foo() {
const result1 = await new Promise((resolve) =>
setTimeout(() => resolve("1")),
);
const result2 = await new Promise((resolve) =>
setTimeout(() => resolve("2")),
);
}
foo();
直接new Promise() 的 resolve 回應給 await
function resolveAfter2Seconds() {
console.log("starting slow promise");
return new Promise((resolve) => {
setTimeout(() => {
resolve("slow");
console.log("slow promise is done");
}, 2000);
});
}
function resolveAfter1Second() {
console.log("starting fast promise");
return new Promise((resolve) => {
setTimeout(() => {
resolve("fast");
console.log("fast promise is done");
}, 1000);
});
}
async function concurrent2() {
console.log("== concurrent2 starts ==");
await Promise.all([
(async () => console.log(await resolveAfter2Seconds()))(),
(async () => console.log(await resolveAfter1Second()))(),
]);
console.log("== concurrent2 done ==");
}
concurrent2();
使用 Promise.all()
來同時執行多個await function
以下是最精簡的寫法
(async () => {
console.log( await new Promise( (resolve) => resolve(1) ))
})();
( () => {
console.log("OK");
} )();
(async () => {
console.log( await Promise.resolve(2) );
})();
( () => { "OK2" } )();