Modern JavaScript Cheatsheet (Part 2)
Bài đăng này đã không được cập nhật trong 6 năm
Promise
Một promise
là 1 object mà có thể được trả về 1 cách đồng bộ từ 1 hàm bất đồng bộ (ref). Promise có thể được sử dụng để tránh callback hell và nó càng ngày càng được thấy nhiều hơn trong các dự án JavaScript hiện đại.
Ví dụ
const fetchingPosts = new Promise((res, rej) => {
$.get("/posts")
.done(posts => res(posts))
.fail(err => rej(err));
});
fetchingPosts
.then(posts => console.log(posts))
.catch(err => console.log(err));
Giải thích
Khi chúng ta thực hiện 1 Ajax request, response là bất đồng bộ vì resource sẽ cần 1 khoảng thời gian để được trả về. Nó thậm chí có thể không được trả về nếu như resource chúng ta yêu cầu không tồn tại vì 1 lí do nào đó ví dụ như 404.
Để giải quyết tình trạng đó, ES2015 cho chúng ta promise
. Promise có thể có 3 trạng thái:
- Pending
- Fulfilled
- Rejected
Giả sử chúng ta muốn dùng promise để xử lý 1 Ajax request để lấy về resource X.
Khởi tạo promise
Đầu tiên chúng ta khởi tạo 1 promise. Chúng ta sẽ sử dụng method get
của jQuery để thực hiện Ajax request đến X.
const xFetcherPromise = new Promise( // Khởi tạo promise với từ khoá "new" và lưu nó vào 1 biến
function(resolve, reject) { // Promise constructor nhận vào 1 function với 2 tham số resolve và reject
$.get("X") // Thực hiện Ajax request
.done(function(X) { // Khi request kết thúc...
resolve(X); // ... resolve promise với tham số X
})
.fail(function(error) { // Khi request thất bại...
reject(error); // ... reject promise với tham số error
});
}
)
Như được thấy ở ví dụ trên, object promise nhận vào 1 executor function với 2 tham số resolve
và reject
. Những tham số này cũng là các function mà khi chúng được gọi sẽ thay đổi trạng thái của promise từ pending
thành fulfilled
hoặc rejected
.
Promise ở trạng thái pending
sau khi instance được khởi tạo và executor function của nó được thực thi ngay lập tức. Khi 1 trong 2 function resolve
hoặc reject
được gọi trong executor function, promise sẽ gọi handler tương ứng.
Sử dụng promise handler
Để lấy được kết quả (hoặc lỗi) của promise, chúng ta phải gắn handler vào cho nó như sau
xFetcherPromise
.then(function(X) {
console.log(X);
})
.catch(function(err) {
console.log(err)
})
Nếu promise thành công, resolve
được gọi và function là tham số của .then
sẽ được thực thi. Nếu promise thất bại, reject
được gọi và function là tham số của .catch
sẽ được thực thi.
Tham khảo
- JavaScript Promises for dummies - Jecelyn Yeen
- JavaScript Promise API - David Walsh
- Using promises - MDN
- What is a promise - Eric Elliott
- JavaScript Promises: an Introduction - Jake Archibald
- Promise documentation - MDN
Async / Await
Ngoài promise
ra thì cũng có 1 cú pháp mới có tên là async
/ await
cho phép chúng ta xử lí các đoạn code bất đồng bộ.
Mục đích của async/await là để giúp đơn giản hóa việc sử dụng promise. Các hàm async luôn trả về 1 promise còn await thì phải được sử dụng trong 1 hàm async. Chúng ta không thể sử dụng await ở top level trong code.
Ví dụ
async function getGithubUser(username) { // từ khóa async cho phép sử dụng await trong hàm và báo trước hàm này trả về 1 promise
const response = await fetch(`https://api.github.com/users/${username}`); // Việc thực thi được hoãn lại tới khi promise trả về bởi fetch được resolve
return response.json();
}
getGithubUser('mbeaudru')
.then(user => console.log(user)) // log user response
.catch(err => console.log(err)); // nếu có lỗi xảy ra trong hàm async, chúng ta sẽ catch nó ở đây
Giải thích
Từ khóa async
đánh dấu 1 hàm là bất đồng bộ và luôn trả về 1 promise. Chúng ta có thể sử dụng từ khóa await
trong 1 hàm async để hoãn việc thực thi ở dòng đó cho đến khi promise resolve hoặc reject.
async function myFunc() {
// có thể sử dụng await ở đây thì hàm này là async
return "hello world";
}
myFunc().then(msg => console.log(msg)) // "hello world" -- hàm myFunc trả về 1 promise do đã sử dụng từ khóa async
Khi chạy đến câu lệnh return
của 1 hàm async, 1 promise được fullfill với giá trị trả về của hàm. Nếu có lỗi xảy ra bên trong hàm async, trạng thái của promise được chuyển thành rejected
. Nếu không có giá trị được trả về từ hàm, 1 promise vẫn được trả về khi hàm kết thúc và resolve với value trống.
await
được sử dụng để chờ promise được fullfill và chỉ có thể được dùng trong 1 hàm async.
Hãy cùng xem chúng ta có thể lấy thông tin của 1 GitHub user với promise như thế nào
function getGithubUser(username) {
return fetch(`https://api.github.com/users/${username}`).then(response => response.json());
}
getGithubUser('mbeaudru')
.then(user => console.log(user))
.catch(err => console.log(err));
Ở đây fetch
là 1 hàm trả về 1 promise cho phép thực hiện Ajax request.
Còn đây là cách viết với async/await
async function getGithubUser(username) { // promise + await keyword usage allowed
const response = await fetch(`https://api.github.com/users/${username}`); // Execution stops here until fetch promise is fulfilled
return response.json();
}
getGithubUser('mbeaudru')
.then(user => console.log(user))
.catch(err => console.log(err));
async/await đặc biệt hữu ích khi chúng ta muốn chain nhiều promise phụ thuộc lẫn nhau. Ví dụ chúng ta cần phải lấy token để có thể lấy được 1 blog post và rồi thông tin của author
async function fetchPostById(postId) {
const token = (await fetch('token_url')).json().token;
const post = (await fetch(`/posts/${postId}?token=${token}`)).json();
const author = (await fetch(`/users/${post.authorId}`)).json();
post.author = author;
return post;
}
fetchPostById('gzIrzeo64')
.then(post => console.log(post))
.catch(err => console.log(err));
Error handling
Nếu không thêm block try
/catch
vào quanh await thì những exception không được bắt sẽ reject promise trả về bởi hàm async. Ngoài ra, sử dụng throw
trong hàm async cũng giống với trả về 1 promise mà bị reject.
Đây là cách chúng ta xử lí error chain với promise
function getUser() { // promise này bị reject
return new Promise((res, rej) => rej("User not found !"));
}
function getAvatarByUsername(userId) {
return getUser(userId).then(user => user.avatar);
}
function getUserAvatar(username) {
return getAvatarByUsername(username).then(avatar => ({ username, avatar }));
}
getUserAvatar('mbeaudru')
.then(res => console.log(res))
.catch(err => console.log(err)); // "User not found !"
Còn đây là cách viết tương ứng với async / await
async function getUser() { // promise được trả về sẽ bị reject
throw "User not found !";
}
async function getAvatarByUsername(userId) => {
const user = await getUser(userId);
return user.avatar;
}
async function getUserAvatar(username) {
var avatar = await getAvatarByUsername(username);
return { username, avatar };
}
getUserAvatar('mbeaudru')
.then(res => console.log(res))
.catch(err => console.log(err)); // "User not found !"
Tham khảo
- Async/Await - JavaScript.Info
- ES7 Async/Await
- 6 Reasons Why JavaScript’s Async/Await Blows Promises Away
- JavaScript awaits
- Using Async Await in Express with Node 8
- Async Function
- Await
- Using async / await in express with node 8
Import / Export
Ví dụ
Named export
Named export có thể được sử dụng để export nhiều giá trị từ 1 module.
// mathConstants.js
export const pi = 3.14;
export const exp = 2.7;
export const alpha = 0.35;
// -------------
// myFile.js
import { pi, exp } from './mathConstants.js'; // Named import
console.log(pi) // 3.14
console.log(exp) // 2.7
// -------------
// mySecondFile.js
import * as constants from './mathConstants.js'; // Inject tất cả các giá trị đã được export vào biến constants
console.log(constants.pi) // 3.14
console.log(constants.exp) // 2.7
Named import trông có vẻ giống với destructuring nhưng nó có 1 syntax khác và không hoàn toàn giống. Nó không support giá trị mặc định hay deep destructuring.
Chúng ta có thể sử dụng alias nhưng syntax cũng khác với destructuring:
import { foo as bar } from 'myFile.js'; // foo được import và inject vào 1 biến mới có tên bar
Default import / export
Đối với mỗi module, chỉ có duy nhất 1 default export. Default export có thể là 1 function, 1 class, 1 object hay bất kì cái gì khác. Giá trị này được xem là giá trị chính được export và nó cũng là cái đơn giản nhất để import.
// coolNumber.js
const ultimateNumber = 42;
export default ultimateNumber;
// ------------
// myFile.js
import number from './coolNumber.js';
// Default export được tự động inject vào biến nummber
console.log(number) // 42
Function exporting:
// sum.js
export default function sum(x, y) {
return x + y;
}
// -------------
// myFile.js
import sum from './sum.js';
const result = sum(1, 2);
console.log(result) // 3
Tham khảo
- ES6 Modules in bulletpoints
- Export - MDN
- Import - MDN
- Understanding ES6 Modules
- Destructuring special case - import statements
- Misunderstanding ES6 Modules - Kent C. Dodds
- Modules in JavaScript
Class
JavaScript là 1 ngôn ngữ prototype-based như kể từ ES6 thì chúng ta cũng có thể sử dụng class trong JavaScript.
Ví dụ
Trước ES6, cú pháp prototype:
var Person = function(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.stringSentence = function() {
return "Hello, my name is " + this.name + " and I'm " + this.age;
}
Với cú pháp class của ES6:
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
stringSentence() {
return "Hello, my name is " + this.name + " and I'm " + this.age;
}
}
const myPerson = new Person("Manu", 23);
console.log(myPerson.age) // 23
console.log(myPerson.stringSentence()) // "Hello, my name is Manu and I'm 23
Tham khảo
Prototype
- Understanding Prototypes in JS - Yehuda Katz
- A plain English guide to JS prototypes - Sebastian Porto
- Inheritance and the prototype chain - MDN
Class
Từ khoá extends
và super
Từ khoá extends
được sử dụng trong khai báo class để tạo ra 1 class là con của 1 class khác. Class con (subclass) sẽ kế thừa tất cả các thuộc tính của class cha (superclass), ngoài ra nó cũng có thể thêm các thuộc tính mới hoặc thay đổi những thuốc tính đã kế thừa.
Từ khoá super
được sử dụng để gọi hàm đối với object cha của 1 object, bao gồm cả constructor của nó.
- Từ khoá
super
phải được dùng trước khi từ khoáthis
được dùng trong constructor - Gọi
super()
sẽ gọi constructor của class cha. Nếu bạn muốn truyền biến vào constructor của class cha, bạn có thể gọisuper(arguments)
- Nếu class cha có 1 hàm tên X, bạn có thể dùng
super.X()
để gọi hàm đó trong class con.
Ví dụ
class Polygon {
constructor(height, width) {
this.name = 'Polygon';
this.height = height;
this.width = width;
}
getHelloPhrase() {
return `Hi, I am a ${this.name}`;
}
}
class Square extends Polygon {
constructor(length) {
// Gọi constructor của class cha với lengh được gán cho width và height của Polygon
super(length, length);
// Chú ý: ở class con, 'super()' phải được gọi trước khi dùng 'this' nếu không sẽ xảy ra lỗi reference error
this.name = 'Square';
this.length = length;
}
getCustomHelloPhrase() {
const polygonPhrase = super.getHelloPhrase(); // gọi hàm X của class cha với cú pháp 'super.X()'
return `${polygonPhrase} with a length of ${this.length}`;
}
get area() {
return this.height * this.width;
}
}
const mySquare = new Square(10);
console.log(mySquare.area) // 100
console.log(mySquare.getHelloPhrase()) // 'Hi, I am a Square' -- Square kế thừa từ Polygon và có thể truy cập tới các hàm của Polygon
console.log(mySquare.getCustomHelloPhrase()) // 'Hi, I am a Square with a length of 10'
Nếu chúng ta dùng this
trước khi gọi super()
trong class Square
thì sẽ xảy ra lỗi ReferenceError
class Square extends Polygon {
constructor(length) {
this.height; // ReferenceError, super cần được gọi đầu tiên!
super(length, length);
this.name = 'Square';
}
}
Tham khảo
Anamorphism và Catamorphism
Anamorphism
Anamorphism là 1 hàm map từ 1 object thành 1 structure phức tạp hơn có chứa type của object đó. Nói cách khác, nó là quá trình unfold
1 cấu trúc đơn giản thành 1 cái phức tạp hơn. Hãy xem xét 1 ví dụ unfold 1 số nguyên thành 1 list các số nguyên. Số nguyên là object ban đầu và list các số nguyên là 1 cấu trúc phức tạp hơn.
function downToOne(n) {
const list = [];
for (let i = n; i > 0; --i) {
list.push(i);
}
return list;
}
downToOne(5)
//=> [ 5, 4, 3, 2, 1 ]
Catamorphism
Ngược với anamorphism, catamorphism nhận vào các object thuộc về 1 cấu trúc phức tạp và fold
chúng thành 1 cái đơn giản hơn. Hãy cùng xem xét ví dụ dưới đây: hàm product
nhận vào 1 list các số nguyên và trả về 1 số nguyên duy nhất
function product(list) {
let product = 1;
for (const n of list) {
product = product * n;
}
return product;
}
product(downToOne(5)) // 120
Tham khảo
Generator
Có 1 cách khác để viết hàm downToOne
ở ví dụ trong phần trên, đó là dùng Generator
. Để khởi tạo 1 generator, chúng a dùng cú pháp function *
. Generator là 1 hàm mà có thể kết thúc và sau đó lại được chạy với context (variable binding) không đổi.
Ví dụ
Hàm downToOne
ở trên có thể được viết lại thành
function * downToOne(n) {
for (let i = n; i > 0; --i) {
yield i;
}
}
[...downToOne(5)] // [ 5, 4, 3, 2, 1 ]
Generator trả về 1 iterable object. Khi hàm next()
của iterator được gọi, nó sẽ chạy cho đến khi gặp từ khóa yield
đầu tiên, yield
được dùng để chỉ rõ giá trị được trả ra từ iterator. Khi return
đã được gọi trong generator thì nó sẽ được coi là hoàn thành và trả về giá trị. Kể cả nếu tiếp tục gọi next()
thì cũng không có giá trị nào được trả về.
// Yield Example
function * idMaker() {
var index = 0;
while (index < 2) {
yield index;
index = index + 1;
}
}
var gen = idMaker();
gen.next().value; // 0
gen.next().value; // 1
gen.next().value; // undefined
yield*
có thể được dùng để cho phép 1 generator gọi 1 generator khác trong iteration.
// Yield * Example
function * genB(i) {
yield i + 1;
yield i + 2;
yield i + 3;
}
function * genA(i) {
yield i;
yield* genB(i);
yield i + 10;
}
var gen = genA(10);
gen.next().value; // 10
gen.next().value; // 11
gen.next().value; // 12
gen.next().value; // 13
gen.next().value; // 20
// Generator Return Example
function* yieldAndReturn() {
yield "Y";
return "R";
yield "unreachable";
}
var gen = yieldAndReturn()
gen.next(); // { value: "Y", done: false }
gen.next(); // { value: "R", done: true }
gen.next(); // { value: undefined, done: true }
Tham khảo
Static method
Từ khóa static
được dùng trong các class để khai báo static method. Static method là các hàm chỉ thuộc về object class mà không thuộc về bất kì instance nào của class đó.
Ví dụ
class Repo {
static getName() {
return "Repo name is modern-js-cheatsheet"
}
}
// Chúng ta không cần phải tạo instance của class Repo
console.log(Repo.getName()) // Repo name is modern-js-cheatsheet
let r = new Repo();
console.log(r.getName()) // Uncaught TypeError: repo.getName is not a function
Gọi static method từ static method
Để gọi 1 static method từ 1 static method khác chúng ta có thể sử dụng từ khóa this
class Repo {
static getName() {
return "Repo name is modern-js-cheatsheet"
}
static modifyName() {
return this.getName() + '-added-this'
}
}
console.log(Repo.modifyName()) // Repo name is modern-js-cheatsheet-added-this
Gọi static method từ non-static method
Non-static method có thể gọi static method bằng 2 cách:
-
Dùng class name
class Repo { static getName() { return "Repo name is modern-js-cheatsheet" } useName() { return Repo.getName() + ' and it contains some really important stuff' } } let r = new Repo() console.log(r.useName()) // Repo name is modern-js-cheatsheet and it contains some really important stuff
-
Dùng constructor
class Repo { static getName() { return "Repo name is modern-js-cheatsheet" } useName() { // Calls the static method as a property of the constructor return this.constructor.getName() + ' and it contains some really important stuff' } } let r = new Repo() console.log(r.useName()) // Repo name is modern-js-cheatsheet and it contains some really important stuff
Tham khảo
Truthy / Falsy
Trong JavaScript, 1 giá trị truthy
or falsy
là 1 giá trị mà sẽ được chuyển kiểu (cast) thành boolean khi được đánh giá trong 1 boolean context. Một ví dụ của boolean context là câu điều kiện if
.
Mọi giá trị sẽ được chuyển kiểu thành true
trừ khi nó bằng với
false
0
""
(string rỗng)null
undefined
NaN
Dưới đây là những ví dụ của boolean context:
-
Câu điều kiện
if
if (myVar) {}
-
Sau operator NOT
!
Operator này trả về
false
nếu như operand của nó có thể được chuyển kiểu thànhtrue
, ngược lại trả vềtrue
!0 // true -- 0 là falsy nên nó trả về true !!0 // false -- 0 là falsy nên !0 trả về true nên !(!0) trả về false !!"" // false -- empty string là falsy nên NOT (NOT false) bằng false
-
Với constructor của Boolean object
new Boolean(0) // false new Boolean(1) // true
-
Trong ternary evaluation
myVar ? "truthy" : "falsy"
Ở đây
myVar
được đánh giá trong 1 boolean context.
Hãy cẩn thận khi so sánh 2 value. Object value sẽ không được chuyển kiểu thành boolean mà nó sẽ được chuyển thành 1 primitive value sử dụng ToPrimitives specification. Khi 1 object được so sánh với giá trị boolean như [] == true
, nó sẽ được thực thi thành [].toString() == true
let a = [] == true // a là false vì [].toString() trả về ""
let b = [1] == true // b là true vì [1].toString() trả về "1"
let c = [2] == true // c là false vì [2].toString() trả về "2"
Tham khảo
Nguồn bài viết
All rights reserved