Những điều cần biết về Promise - JavaScript
Bài đăng này đã không được cập nhật trong 4 năm
Chào anh em , Promise
có thể nhiều anh em đang sử dụng nó hằng ngày, hằng giờ nhưng thực chất vẫn chưa hiểu hết về nó. Vì vậy hôm này cùng mình tìm hiểu kĩ hơn về thằng này nhé. Bắt đầu
Chuẩn bị
Một vài kiến thức cần biết:
- Javascript
Môi trường sẽ demo:
- Bất kì
Editor code
nào, kể cả online
Bỏ qua
- Các lý thuyết cụ thể về Promise
Promise, nó là cái gì ?
JavaScript là một ngôn ngữ lập trình đồng bộ
(đơn luồng), có nghĩa là chỉ một điều gì đó có thể xảy ra tại một thời điểm, JavaScript engine
chỉ có thể xử lý một câu lệnh tại một thời điểm trong một luồng.
Promise
được sinh ra như một đối tượng đặc biệt dùng cho các xử lý bất đồng bộ
. Nó đại diện cho một xử lý bất đồng bộ và chứa kết quả cũng như lỗi xảy ra từ xử lý bất đồng bộ đó.
Trạng thái
Tại mỗi thời điểm Promise sẽ có 1 trạng thái nhất đinh
pending
fulfilled
rejected
Prototype
Properties
constructor(callback)
Methods
then(onFulfilled[, onRejected])
catch(onRejected)
finally(onFinally)
Static methods
Promise.resolve(value)
: Trả về một promise resolved (hoàn thành) với một giá trị cụ thể.Promise.reject(reason)
: Trả về một promise rejected (lỗi) với một lỗi cụ thể.Promise.all(iterable of multiple promises)
- Nhận vào một mảng các promise hoặc cũng có thể là non-promise (number, boolean, .etc).
- Trả về một promise mới.
- Chờ tất cả các promise trong mảng resolved, kết quả của promise mới này là một mảng chứa kết quả của các promise theo đúng thứ tự.
- 1 promise bất kì rejected, kết quả của promise mới này được trả về ngay lập tức lỗi của promise rejected đó, các promise khác vẫn tiếp tục thực thi nhưng kết quả bị bỏ qua.
- Thực thi promise dạng parallel.
Promise.race(iterable of multiple promises)
:- Nhận vào một mảng các promise hoặc cũng có thể là non-promise (number, boolean, .etc).
- Trả về một promise mới.
- Kết quả của promise mới là kết quả của promise bất kì đầu tiên resolved hoặc rejected.
Promise.allSetted(iterable of multiple promises)
:- Nhận vào một mảng các promise hoặc cũng có thể là non-promise (number, boolean, .etc).
- Trả về một promise mới.
- Chờ cho tất cả các promise được xử lý, kết quả trả về là mảng chứa các object chứa trạng thái và giá trị của promise kể cả resolved hay rejected.
Một vài trình duyệt với phiên bản mới gần đây đã hỗ trợ
Promise.any(iterable of multiple promises)
:experimental
Mô phỏng về promise
Promise là 1 constructor function
(es5) hay cũng có thể gọi nó là 1 class
(es6)
Mình sẽ mô phỏng nó bằng es6
class MyPromise {
constructor(callback) {
console.log("prototype method constructor")
}
then(onFulfilled) {
console.log("prototype method then")
return this
}
catch(onRejected) {
console.log("prototype method catch")
return this
}
finally(onFinally) {
console.log("prototype method finally")
}
static resolve(value) {
console.log('static method resolve', value)
}
static all(array) {
console.log('static method all', array)
}
}
MyPromise.propertyX = "property"
const instance = new MyPromise()
// prototype property
console.log(instance.constructor)
// prototype method
instance.then().catch().finally()
// static property
console.log(MyPromise.propertyX)
// static method
MyPromise.resolve('xxx')
MyPromise.all(['yyy', 'zzz'])
// OUTPUT
// prototype method constructor
// { [Function: MyPromise] propertyX: 'property' }
// prototype method then
// prototype method catch
// prototype method finally
// property
// static method resolve xxx
// static method all [ 'yyy', 'zzz' ]
dĩ nhiên đây chỉ là mô phỏng thôi, còn thực thế thì xử lý bên trong của nó cồng kềnh hơn nhiều.
Sử dụng thực tế
Cách sử dụng cơ bản nhất
const p1 = new Promise((resolve, reject) => {
resolve('xxx success')
})
const p2 = new Promise((resolve, reject) => {
reject('yyy error')
})
p1.then(success => {
console.log(success)
})
p2.catch(error => {
console.log(error)
})
p1
.then(success => {
console.log(success)
})
.catch(error => {
console.log(error)
})
.finally(() => {
console.log('finally')
})
// xxx success
// yyy error
// xxx success
// finally
all, race, allSetted
all
const p1 = Promise.resolve(100)
const p2 = Promise.resolve(true)
const p3 = Promise.reject('error')
const p4 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve("p4")
}, 2000)
})
Promise.all([p1,p2,p4])
.then(data => {
console.log('p1,p2,p4', data)
})
.catch(err => {
console.log('p1,p2,p4', err)
})
Promise.all([p1,p2,p3,p4])
.then(data => {
console.log('p1,p2,p3,p4', data)
})
.catch(err => {
console.log('p1,p2,p3,p4', err)
})
// p1,p2,p3,p4 error
// p1,p2,p4 [ 100, true, 'p4' ]
Tất nhiên là khi có lỗi thì ngay lập tức promise chuyển sang trạng thái rejected
và cũng như trả về luôn kết quả, nhưng hãy nhớ là các promise khác vẫn được thực hiện nhưng kết quả bị bỏ qua.
race
const p1 = Promise.resolve(100)
const p2 = Promise.resolve(true)
const p3 = Promise.reject('error')
const p4 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve("p4")
}, 2000)
})
Promise.race([p1,p2,p4])
.then(data => {
console.log('p1,p2,p4', data)
})
.catch(err => {
console.log('p1,p2,p4', err)
})
Promise.race([p1,p2,p3,p4])
.then(data => {
console.log('p1,p2,p3,p4', data)
})
.catch(err => {
console.log('p1,p2,p3,p4', err)
})
// p1,p2,p4 100
// p1,p2,p3,p4 100
Tất nhiên là promise đầu tiên thành công thì sẽ trả về kết quả ngay lập tức.
allSettled
const p1 = Promise.resolve(100)
const p2 = Promise.resolve(true)
const p3 = Promise.reject('error')
const p4 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve("p4")
}, 2000)
})
Promise.allSettled([p1,p2,p4])
.then(data => {
console.log('p1,p2,p4', data)
})
.catch(err => {
console.log('p1,p2,p4', err)
})
Promise.allSettled([p1,p2,p3,p4])
.then(data => {
console.log('p1,p2,p3,p4', data)
})
.catch(err => {
console.log('p1,p2,p3,p4', err)
})
// "p1,p2,p4" Array [Object { status: "fulfilled", value: 100 }, Object { status: "fulfilled", value: true }, Object { status: "fulfilled", value: "p4" }]
// "p1,p2,p3,p4" Array [Object { status: "fulfilled", value: 100 }, Object { status: "fulfilled", value: true }, Object { status: "rejected", reason: "error" }, Object { status: "fulfilled", value: "p4" }
Chaining promises
- Đơn giản
const p1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('Promise 1')
})
})
const p2 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('Promise 2')
})
})
const p3 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('Promise 3')
})
})
p1.then((data) => {
console.log(data)
return p2
})
.then((data) => {
console.log(data)
return p3
})
.then((data) => {
console.log(data)
})
// Promise 1
// Promise 2
// Promise 3
- Hàm trả về promise
const p1 = data => new Promise((resolve, reject) => {
setTimeout(() => {
console.log('Promise 1')
resolve(data)
})
})
const p2 = data => new Promise((resolve, reject) => {
setTimeout(() => {
console.log('Promise 2')
resolve(data)
})
})
const p3 = data => new Promise((resolve, reject) => {
setTimeout(() => {
console.log('Promise 3')
resolve(data)
})
})
p1(1).then((data) => {
console.log(data)
return p2(data + 1)
})
.then((data) => {
console.log(data)
return p3(data + 1)
})
.then((data) => {
console.log(data)
})
// Promise 1
// 1
// Promise 2
// 2
// Promise 3
// 3
Một vài lưu ý
Catch error và callback error của then
Callback lỗi không bắt được lỗi khi khối promise resolved và trong callback của then có throw Error
const resolvePromise = new Promise((resolve, reject) => {
resolve('OK')
})
const rejectPromise = new Promise((resolve, reject) => {
reject('Error')
})
rejectPromise
.then(() => {
throw new Error('Oh no')
})
.catch(err => {
console.log('rejectPromise, Catch lỗi', err)
})
rejectPromise
.then(() => {
throw new Error('Oh no')
}, err => {
console.log('rejectPromise, Callback lỗi', err)
})
resolvePromise
.then(() => {
throw new Error('Oh no')
})
.catch(err => {
console.log('resolvePromise, Catch lỗi', err)
})
resolvePromise
.then(() => {
throw new Error('Oh no')
}, err => {
console.log('resolvePromise, Callback lỗi', err)
})
// rejectPromise, Callback lỗi Error
// rejectPromise, Catch lỗi Error
// resolvePromise, Catch lỗi Error: Oh no
return và không return trong callback của then (khi sử dụng tính chất chaining promises)
- Có return
const promise = new Promise((resolve, reject) => {
resolve(100)
})
promise.then(data1 => {
console.log("ok 1", data1)
return data1 * 2
})
.then(data2 => {
console.log("ok 2", data2)
})
.catch(err => {
console.log('error', err)
})
.finally(() => {
console.log('done')
})
// ok 1 100
// ok 2 200
// done
- Không return
const promise = new Promise((resolve, reject) => {
resolve(100)
})
promise.then(data1 => {
console.log("ok 1", data1)
})
.then(data2 => {
console.log("ok 2", data2)
})
.catch(err => {
console.log('error', err)
})
.finally(() => {
console.log('done')
})
// ok 1 100
// ok 2 undefined
// done
Micro-Task Queue (ES8 term)
Khi 1 promise đã sẵn sàng, .then/catch/finally
của nó được đẩy vào queue
, chúng chưa được thực hiện ngay. Khi JavaScript engine
đã hoàn thành code hiện tại (hoàn thành các task synchronous), nó lấy các task từ queue
và thực thi nó.
(Để hiểu kĩ hơn về cách JavaScript engine
hoạt động như thế nào thì phần này bạn phải tìm hiểu thêm về event loop
nhé)
console.log('start')
setTimeout(() => {
console.log('setTimeout done')
}, 0)
const promise1 = Promise.resolve()
const promise2 = Promise.reject()
promise1
.then(() => {
console.log('promise1 then 1 ok')
})
.then(() => {
console.log('promise1 then 2 ok')
})
.catch(() => {
console.log('catch 1')
})
promise2
.then(() => {
console.log('promise2 then 1 ok')
})
.then(() => {
console.log('promise2 then 2 ok')
})
.catch(() => {
console.log('catch 2')
})
console.log('stop')
// start
// stop
// promise1 then 1 ok
// promise1 then 2 ok
// catch 2
// setTimeout done
Các bạn thấy đó, sau task các log đồng bộ thì sẽ đến các log của promise
sau đó mới đến setTimeout
dù chỉ set là 0ms
, bởi vì độ ưu tiên của Micro-Task Queue
cao hơn Message Queue
nên promise sẽ được thực thi trước callback bên trong setTimeout
Thứ tự xử lý callback
của then/catch/finally
trong queue
const promise1 = Promise.reject()
const promise2 = Promise.resolve()
const promise3 = Promise.resolve()
promise1
.then(() => {
console.log('promise1 then')
})
.catch(() => {
console.log('promise1 catch')
})
.finally(() => {
console.log('promise1 finally')
})
promise2
.then(() => {
console.log('promise2 then')
})
.catch(() => {
console.log('promise2 catch')
})
.finally(() => {
console.log('promise2 finally')
})
promise3
.then(() => {
console.log('promise3 then')
})
.catch(() => {
console.log('promise3 catch')
})
.finally(() => {
console.log('promise3 finally')
})
// promise2 then
// promise3 then
// promise1 catch
// promise1 finally
// promise2 finally
// promise3 finally
Tổng kết
Anh em thấy đấy promise in javascript
phức tạp hơn những gì chúng ta nghĩ.
Qua bài viết này hi vọng sẽ giúp ích phần nào đó cho anh em.
Cảm ơn anh em đã đọc bài viết này. Chúc anh em thành công nhé .
All rights reserved