總結
本篇筆記內容:
- JavaScript 的非同步處理如何從呼叫 Callback function 轉換為 Promise 的形式
- AC 學期 3 的「Promise 包裝作業」思考流程
筆記
Promise
定義:
- Promises/A+:
- A promise represents the eventual result of an asynchronous operation.
- The primary way of interacting with a promise is through its
thenmethod, which registers callbacks to receive either a promise’s eventual value or the reason why the promise cannot be fulfilled.
- MDN:
- The Promise object represents the eventual completion (or failure) of an asynchronous operation and its resulting value.
- This lets asynchronous methods return values like synchronous methods: instead of immediately returning the final value, the asynchronous method returns a promise to supply the value at some point in the future.
Promise.prototype.then(),Promise.prototype.catch(): As thethenandPromise.prototype.catch()methods return promises, they can be chained — an operation called composition.Promise.prototype.finally(): This provides a way for code to be run whether the promise was fulfilled successfully or rejected once thePromisehas been dealt with.
小結論:
- promise 代表一個非同步功能的最終結果
- async function 最終的結果會是
fulfilled或rejected二選一 - 用
then()來接Promise.resolve()的值、用catch()來接Promise.reject(),而finally()內的 function 則是不論結果為 resolve 或 reject 都會執行
callback 使用情境
範例:以下程式碼全部借鑑 YouTube 影片Async JS Crash Course - Callbacks, Promises, Async Await
const posts = [
{ title: "Post 1", content: "Content post 1" },
{ title: "Post 2", content: "Content post 2" },
];
function postToList() {
setTimeout(() => {
let content = "";
posts.forEach((post) => {
content += `<li>${post.title}</li>`;
});
document.body.innerHTML = content;
}, 1000);
}
postToList();
- 模擬情境:
- 瀏覽器向 server 索取 posts 資料,而 server 過了一秒後回覆
- 瀏覽器收到 posts 後,將所有的 post.title 渲染為清單並放到畫面上
const posts = [
{ title: "Post 1", content: "Content post 1" },
{ title: "Post 2", content: "Content post 2" },
];
function postToList() {
setTimeout(() => {
let content = "";
posts.forEach((post) => {
content += `<li>${post.title}</li>`;
});
document.body.innerHTML = content;
}, 1000); // 延後1秒執行
}
function addPost(post) {
setTimeout(() => {
posts.push(post);
}, 2000); // 延後2秒執行
}
addPost({ title: "Post 3", content: "Content post 3" });
postToList();
- 模擬情境:
- 將第三篇 post 新增到 posts 陣列中
- 瀏覽器向 server 索取 posts 資料,而 server 過了一秒後回覆
- 瀏覽器收到 posts 後,將所有的 post.title 渲染為清單並放到畫面上
- 執行結果:畫面上僅會有 post 1 與 post2,因為
addPost()實際執行的時間點(被setTimeout()延後至少 2 秒)比postToList()(被setTimeout()延後至少 1 秒)晚
const posts = [
{ title: "Post 1", content: "Content post 1" },
{ title: "Post 2", content: "Content post 2" },
];
function postToList() {
setTimeout(() => {
let content = "";
posts.forEach((post) => {
content += `<li>${post.title}</li>`;
});
document.body.innerHTML = content;
}, 1000);
}
function addPost(post, callback) {
setTimeout(() => {
posts.push(post);
callback();
}, 2000);
}
addPost({ title: "Post 3", content: "Content post 3" }, postToList);
- callback function 登場,將
postToList作為 callback 傳入addPost後,程式碼的行為變成「待addPost結束後,再執行postToList」 - 執行結果:畫面上會印出 post 1、post2 與 post3
將 callback 轉換為 promise 形式
const posts = [
{ title: "Post 1", content: "Content post 1" },
{ title: "Post 2", content: "Content post 2" },
];
function postToList() {
setTimeout(() => {
let content = "";
posts.forEach((post) => {
content += `<li>${post.title}</li>`;
});
document.body.innerHTML = content;
}, 1000);
}
function addPost(post) {
return new Promise((resolve, reject) => {
setTimeout(() => {
const error = false;
if (!error) {
posts.push(post);
resolve();
} else {
reject("something goes wrong");
}
});
});
}
addPost({ title: "Post 3", content: "Content post 3" })
.then(() => {
postToList();
})
.catch((error) => {
console.error(error);
});
- 移除
addPost的 callback 參數 - 讓
addPost回傳 Promise 物件,其中resolve()沒有包含任何值,而reject()提供錯誤訊息「something goes wrong」 - 執行
addPost後,透過then與catch來承接addPost順利完成(resolve)或失敗(reject)的情境 addPost成功時,進行postToList()addPost出現錯誤時,則console.error(error)
AC 作業應用
原始 callback 版本:
const http = require("http");
const https = require("https");
const imgPath = "";
http
.createServer((req, res) => {
https
.get("https://dog.ceo/api/breeds/image/random", (body) => {
let data = "";
body.on("data", (chunk) => {
data += chunk;
});
body.on("end", () => {
console.log(JSON.parse(data));
imgPath = JSON.parse(data).message;
res.end(`<h1>DOG PAGE</h1><img src='${imgPath}' >`);
});
})
.on("error", (error) => {
console.error(error);
});
})
.listen(3000);
Promise 版起手式:
const http = require("http");
const https = require("https");
const requestData = () => {
// TODO
};
http
.createServer((req, res) => {
// TODO
res.end(`<h1>DOG PAGE</h1><img src='${imgPath}' >`);
})
.listen(3000);
思考過程:
- 用
resolve()處理JSON.parse(data),用reject()處理.on('error', (error) => { console.error(error) })這一段 - 把整個
https.get(...)包成 Promise 物件 requestData順利完成後,用then()來接JSON.parse(data);result內容為JSON.parse(data),取result.message即為圖片網址- 用
catch()來處理錯誤狀態,將workingURL替換為變數errorURL後,即可在終端看到error訊息
改裝完成:
const http = require("http");
const https = require("https");
const workingURL = "https://dog.ceo/api/breeds/image/random";
const errorURL = "https://this.will.not/work";
const requestData = () => {
return new Promise((resolve, reject) => {
https
.get(workingURL, (body) => {
let data = "";
body.on("data", (chunk) => {
data += chunk;
});
body.on("end", () => {
console.log(JSON.parse(data));
resolve(JSON.parse(data));
});
})
.on("error", (error) => {
reject(error);
});
});
};
http
.createServer((req, res) => {
requestData()
.then((result) => {
const imgPath = result.message;
res.end(`<h1>DOG PAGE</h1><img src='${imgPath}' >`);
})
.catch((error) => {
console.error(error);
res.end(`<p>Sorry but something goes wrong.</p>`);
});
})
.listen(3000);
補充:Promise.all()
// MDN範例
const promise1 = Promise.resolve("Answer to the Ultimate Question of Life, ");
const promise2 = "the Universe, and Everything";
const promise3 = new Promise((resolve, reject) => {
setTimeout(resolve, 1000, "42");
});
Promise.all([promise1, promise2, promise3]).then((values) => {
console.log(values);
});
// 輸出結果:
// ["Answer to the Ultimate Question of Life, ", "the Universe, and Everything", "42"]
參考MDN:
- It takes an iterable of promises as an input, and returns a single Promise that resolves to an array of the results of the input promises.
- It rejects immediately upon any of the input promises rejecting or non-promises throwing an error, and will reject with this first rejection message / error.