手把手教你實(shí)現(xiàn) Promise的使用方法
前言
很多 JavaScript 的初學(xué)者都曾感受過(guò)被回調(diào)地獄支配的恐懼,直至掌握了 Promise 語(yǔ)法才算解脫。雖然很多語(yǔ)言都早已內(nèi)置了 Promise ,但是 JavaScript 中真正將其發(fā)揚(yáng)光大的還是 jQuery 1.5 對(duì) $.ajax 的重構(gòu),支持了 Promise,而且用法也和 jQuery 推崇的鏈?zhǔn)秸{(diào)用不謀而合。后來(lái) ES6 出世,大家才開(kāi)始進(jìn)入全民 Promise 的時(shí)代,再后來(lái) ES8 又引入了 async 語(yǔ)法,讓 JavaScript 的異步寫(xiě)法更加優(yōu)雅。
今天我們就一步一步來(lái)實(shí)現(xiàn)一個(gè) Promise,如果你還沒(méi)有用過(guò) Promise,建議先熟悉一下 Promise 語(yǔ)法再來(lái)閱讀本文。
構(gòu)造函數(shù)
在已有的 Promise/A+ 規(guī)范 中并沒(méi)有規(guī)定 promise 對(duì)象從何而來(lái),在 jQuery 中通過(guò)調(diào)用 $.Deferred() 得到 promise 對(duì)象,ES6 中通過(guò)實(shí)例化 Promise 類(lèi)得到 promise 對(duì)象。這里我們使用 ES 的語(yǔ)法,構(gòu)造一個(gè)類(lèi),通過(guò)實(shí)例化的方式返回 promise 對(duì)象,由于 Promise 已經(jīng)存在,我們暫時(shí)給這個(gè)類(lèi)取名為 Deferred 。
class Deferred {
constructor(callback) {
const resolve = () => {
// TODO
}
const reject = () => {
// TODO
}
try {
callback(resolve, reject)
} catch (error) {
reject(error)
}
}
}
構(gòu)造函數(shù)接受一個(gè) callback,調(diào)用 callback 的時(shí)候需傳入 resolve、reject 兩個(gè)方法。
Promise 的狀態(tài)
Promise 一共分為三個(gè)狀態(tài):

pending :等待中,這是 Promise 的初始狀態(tài);
fulfilled :已結(jié)束,正常調(diào)用 resolve 的狀態(tài);
rejected :已拒絕,內(nèi)部出現(xiàn)錯(cuò)誤,或者是調(diào)用 reject 之后的狀態(tài);

我們可以看到 Promise 在運(yùn)行期間有一個(gè)狀態(tài),存儲(chǔ)在 [[PromiseState]] 中。下面我們?yōu)?Deferred 添加一個(gè)狀態(tài)。
//基礎(chǔ)變量的定義
const STATUS = {
PENDING: 'PENDING',
FULFILLED: 'FULFILLED',
REJECTED: 'REJECTED'
}
class Deferred {
constructor(callback) {
this.status = STATUS.PENDING
const resolve = () => {
// TODO
}
const reject = () => {
// TODO
}
try {
callback(resolve, reject)
} catch (error) {
// 出現(xiàn)異常直接進(jìn)行 reject
reject(error)
}
}
}
這里還有個(gè)有意思的事情,早期瀏覽器的實(shí)現(xiàn)中 fulfilled 狀態(tài)是 resolved,明顯與 Promise 規(guī)范不符。當(dāng)然,現(xiàn)在已經(jīng)修復(fù)了。

內(nèi)部結(jié)果
除開(kāi)狀態(tài),Promise 內(nèi)部還有個(gè)結(jié)果 [[PromiseResult]] ,用來(lái)暫存 resolve/reject 接受的值。


繼續(xù)在構(gòu)造函數(shù)中添加一個(gè)內(nèi)部結(jié)果。
class Deferred {
constructor(callback) {
this.value = undefined
this.status = STATUS.PENDING
const resolve = value => {
this.value = value
// TODO
}
const reject = reason => {
this.value = reason
// TODO
}
try {
callback(resolve, reject)
} catch (error) {
// 出現(xiàn)異常直接進(jìn)行 reject
reject(error)
}
}
}
儲(chǔ)存回調(diào)
使用 Promise 的時(shí)候,我們一般都會(huì)調(diào)用 promise 對(duì)象的 .then 方法,在 promise 狀態(tài)轉(zhuǎn)為 fulfilled 或 rejected 的時(shí)候,拿到內(nèi)部結(jié)果,然后做后續(xù)的處理。所以構(gòu)造函數(shù)中,還需要構(gòu)造兩個(gè)數(shù)組,用來(lái)存儲(chǔ) .then 方法傳入的回調(diào)。
class Deferred {
constructor(callback) {
this.value = undefined
this.status = STATUS.PENDING
this.rejectQueue = []
this.resolveQueue = []
const resolve = value => {
this.value = value
// TODO
}
const reject = reason => {
this.value = reason
// TODO
}
try {
callback(resolve, reject)
} catch (error) {
// 出現(xiàn)異常直接進(jìn)行 reject
reject(error)
}
}
}
resolve 與 reject
修改狀態(tài)
接下來(lái),我們需要實(shí)現(xiàn) resolve 和 reject 兩個(gè)方法,這兩個(gè)方法在被調(diào)用的時(shí)候,會(huì)改變 promise 對(duì)象的狀態(tài)。而且任意一個(gè)方法在被調(diào)用之后,另外的方法是無(wú)法被調(diào)用的。
new Promise((resolve, reject) => {
setTimeout(() => {
resolve('🙆♂️')
}, 500)
setTimeout(() => {
reject('🙅♂️')
}, 800)
}).then(
() => {
console.log('fulfilled')
},
() => {
console.log('rejected')
}
)

此時(shí),控制臺(tái)只會(huì)打印出 fulfilled ,并不會(huì)出現(xiàn) rejected 。
class Deferred {
constructor(callback) {
this.value = undefined
this.status = STATUS.PENDING
this.rejectQueue = []
this.resolveQueue = []
let called // 用于判斷狀態(tài)是否被修改
const resolve = value => {
if (called) return
called = true
this.value = value
// 修改狀態(tài)
this.status = STATUS.FULFILLED
}
const reject = reason => {
if (called) return
called = true
this.value = reason
// 修改狀態(tài)
this.status = STATUS.REJECTED
}
try {
callback(resolve, reject)
} catch (error) {
// 出現(xiàn)異常直接進(jìn)行 reject
reject(error)
}
}
}
調(diào)用回調(diào)
修改完?duì)顟B(tài)后,拿到結(jié)果的 promise 一般會(huì)調(diào)用 then 方法傳入的回調(diào)。
class Deferred {
constructor(callback) {
this.value = undefined
this.status = STATUS.PENDING
this.rejectQueue = []
this.resolveQueue = []
let called // 用于判斷狀態(tài)是否被修改
const resolve = value => {
if (called) return
called = true
this.value = value
// 修改狀態(tài)
this.status = STATUS.FULFILLED
// 調(diào)用回調(diào)
for (const fn of this.resolveQueue) {
fn(this.value)
}
}
const reject = reason => {
if (called) return
called = true
this.value = reason
// 修改狀態(tài)
this.status = STATUS.REJECTED
// 調(diào)用回調(diào)
for (const fn of this.rejectQueue) {
fn(this.value)
}
}
try {
callback(resolve, reject)
} catch (error) {
// 出現(xiàn)異常直接進(jìn)行 reject
reject(error)
}
}
}
熟悉 JavaScript 事件系統(tǒng)的同學(xué)應(yīng)該知道, promise.then 方法中的回調(diào)會(huì)被放置到微任務(wù)隊(duì)列中,然后異步調(diào)用。

所以,我們需要將回調(diào)的調(diào)用放入異步隊(duì)列,這里我們可以放到 setTimeout 中進(jìn)行延遲調(diào)用,雖然不太符合規(guī)范,但是將就將就。
class Deferred {
constructor(callback) {
this.value = undefined
this.status = STATUS.PENDING
this.rejectQueue = []
this.resolveQueue = []
let called // 用于判斷狀態(tài)是否被修改
const resolve = value => {
if (called) return
called = true
// 異步調(diào)用
setTimeout(() => {
this.value = value
// 修改狀態(tài)
this.status = STATUS.FULFILLED
// 調(diào)用回調(diào)
for (const fn of this.resolveQueue) {
fn(this.value)
}
})
}
const reject = reason => {
if (called) return
called = true
// 異步調(diào)用
setTimeout(() =>{
this.value = reason
// 修改狀態(tài)
this.status = STATUS.REJECTED
// 調(diào)用回調(diào)
for (const fn of this.rejectQueue) {
fn(this.value)
}
})
}
try {
callback(resolve, reject)
} catch (error) {
// 出現(xiàn)異常直接進(jìn)行 reject
reject(error)
}
}
}
then 方法
接下來(lái)我們需要實(shí)現(xiàn) then 方法,用過(guò) Promise 的同學(xué)肯定知道,then 方法是能夠繼續(xù)進(jìn)行鏈?zhǔn)秸{(diào)用的,所以 then 必須要返回一個(gè) promise 對(duì)象。但是在 Promise/A+ 規(guī)范中,有明確的規(guī)定,then 方法返回的是一個(gè)新的 promise 對(duì)象,而不是直接返回 this,這一點(diǎn)我們可以通過(guò)下面代碼驗(yàn)證一下。

可以看到 p1 對(duì)象和 p2 是兩個(gè)不同的對(duì)象,并且 then 方法返回的 p2 對(duì)象也是 Promise 的實(shí)例。
除此之外,then 方法還需要判斷當(dāng)前狀態(tài),如果當(dāng)前狀態(tài)不是 pending 狀態(tài),則可以直接調(diào)用傳入的回調(diào),而不用再放入隊(duì)列進(jìn)行等待。
class Deferred {
then(onResolve, onReject) {
if (this.status === STATUS.PENDING) {
// 將回調(diào)放入隊(duì)列中
const rejectQueue = this.rejectQueue
const resolveQueue = this.resolveQueue
return new Deferred((resolve, reject) => {
// 暫存到成功回調(diào)等待調(diào)用
resolveQueue.push(function (innerValue) {
try {
const value = onResolve(innerValue)
// 改變當(dāng)前 promise 的狀態(tài)
resolve(value)
} catch (error) {
reject(error)
}
})
// 暫存到失敗回調(diào)等待調(diào)用
rejectQueue.push(function (innerValue) {
try {
const value = onReject(innerValue)
// 改變當(dāng)前 promise 的狀態(tài)
resolve(value)
} catch (error) {
reject(error)
}
})
})
} else {
const innerValue = this.value
const isFulfilled = this.status === STATUS.FULFILLED
return new Deferred((resolve, reject) => {
try {
const value = isFulfilled
? onResolve(innerValue) // 成功狀態(tài)調(diào)用 onResolve
: onReject(innerValue) // 失敗狀態(tài)調(diào)用 onReject
resolve(value) // 返回結(jié)果給后面的 then
} catch (error) {
reject(error)
}
})
}
}
}
現(xiàn)在我們的邏輯已經(jīng)可以基本跑通,我們先試運(yùn)行一段代碼:
new Deferred(resolve => {
setTimeout(() => {
resolve(1)
}, 3000)
}).then(val1 => {
console.log('val1', val1)
return val1 * 2
}).then(val2 => {
console.log('val2', val2)
return val2
})
3 秒后,控制臺(tái)出現(xiàn)如下結(jié)果:

可以看到,這基本符合我們的預(yù)期。
值穿透
如果我們?cè)谡{(diào)用 then 的時(shí)候,如果沒(méi)有傳入任何的參數(shù),按照規(guī)范,當(dāng)前 promise 的值是可以透?jìng)鞯较乱粋€(gè) then 方法的。例如,如下代碼:
new Deferred(resolve => {
resolve(1)
})
.then()
.then()
.then(val => {
console.log(val)
})

在控制臺(tái)并沒(méi)有看到任何輸出,而切換到 Promise 是可以看到正確結(jié)果的。

要解決這個(gè)方法很簡(jiǎn)單,只需要在 then 調(diào)用的時(shí)候判斷參數(shù)是否為一個(gè)函數(shù),如果不是則需要給一個(gè)默認(rèn)值。
const isFunction = fn => typeof fn === 'function'
class Deferred {
then(onResolve, onReject) {
// 解決值穿透
onReject = isFunction(onReject) ? onReject : reason => { throw reason }
onResolve = isFunction(onResolve) ? onResolve : value => { return value }
if (this.status === STATUS.PENDING) {
// ...
} else {
// ...
}
}
}

現(xiàn)在我們已經(jīng)可以拿到正確結(jié)果了。
一步之遙
現(xiàn)在我們距離完美實(shí)現(xiàn) then 方法只差一步之遙,那就是我們?cè)谡{(diào)用 then 方法傳入的 onResolve/onReject 回調(diào)時(shí),還需要判斷他們的返回值。如果回調(diào)的內(nèi)部返回的就是一個(gè) promise 對(duì)象,我們應(yīng)該如何處理?或者出現(xiàn)了循環(huán)引用,我們又該怎么處理?
前面我們?cè)谀玫?onResolve/onReject 的返回值后,直接就調(diào)用了 resolve 或者 resolve ,現(xiàn)在我們需要把他們的返回值進(jìn)行一些處理。
then(onResolve, onReject) {
// 解決值穿透代碼已經(jīng)省略
if (this.status === STATUS.PENDING) {
// 將回調(diào)放入隊(duì)列中
const rejectQueue = this.rejectQueue
const resolveQueue = this.resolveQueue
const promise = new Deferred((resolve, reject) => {
// 暫存到成功回調(diào)等待調(diào)用
resolveQueue.push(function (innerValue) {
try {
const value = onResolve(innerValue)
- resolve(value)
+ doThenFunc(promise, value, resolve, reject)
} catch (error) {
reject(error)
}
})
// 暫存到失敗回調(diào)等待調(diào)用
rejectQueue.push(function (innerValue) {
try {
const value = onReject(innerValue)
- resolve(value)
+ doThenFunc(promise, value, resolve, reject)
} catch (error) {
reject(error)
}
})
})
return promise
} else {
const innerValue = this.value
const isFulfilled = this.status === STATUS.FULFILLED
const promise = new Deferred((resolve, reject) => {
try {
const value = isFulfilled
? onResolve(innerValue) // 成功狀態(tài)調(diào)用 onResolve
: onReject(innerValue) // 失敗狀態(tài)調(diào)用 onReject
- resolve(value)
+ doThenFunc(promise, value, resolve, reject)
} catch (error) {
reject(error)
}
})
return promise
}
}
返回值判斷
在我們使用 Promise 的時(shí)候,經(jīng)常會(huì)在 then 方法中返回一個(gè)新的 Promise,然后把新的 Promise 完成后的內(nèi)部結(jié)果再傳遞給后面的 then 方法。
fetch('server/login')
.then(user => {
// 返回新的 promise 對(duì)象
return fetch(`server/order/${user.id}`)
})
.then(order => {
console.log(order)
})
function doThenFunc(promise, value, resolve, reject) {
// 如果 value 是 promise 對(duì)象
if (value instanceof Deferred) {
// 調(diào)用 then 方法,等待結(jié)果
value.then(
function (val) {
doThenFunc(promise, value, resolve, reject)
},
function (reason) {
reject(reason)
}
)
return
}
// 如果非 promise 對(duì)象,則直接返回
resolve(value)
}
判斷循環(huán)引用
如果當(dāng)前 then 方法回調(diào)函數(shù)返回值是當(dāng)前 then 方法產(chǎn)生的新的 promise 對(duì)象,則被認(rèn)為是循環(huán)引用,具體案例如下:

then 方法返回的新的 promise 對(duì)象 p1 ,在回調(diào)中被當(dāng)做返回值,此時(shí)會(huì)拋出一個(gè)異常。因?yàn)榘凑罩暗倪壿嫞a將會(huì)一直困在這一段邏輯里。

所以,我們需要提前預(yù)防,及時(shí)拋出錯(cuò)誤。
function doThenFunc(promise, value, resolve, reject) {
// 循環(huán)引用
if (promise === value) {
reject(
new TypeError('Chaining cycle detected for promise')
)
return
}
// 如果 value 是 promise 對(duì)象
if (value instanceof Deferred) {
// 調(diào)用 then 方法,等待結(jié)果
value.then(
function (val) {
doThenFunc(promise, value, resolve, reject)
},
function (reason) {
reject(reason)
}
)
return
}
// 如果非 promise 對(duì)象,則直接返回
resolve(value)
}
現(xiàn)在我們?cè)僭囋囋?then 中返回一個(gè)新的 promise 對(duì)象。
const delayDouble = (num, time) => new Deferred((resolve) => {
console.log(new Date())
setTimeout(() => {
resolve(2 * num)
}, time)
})
new Deferred(resolve => {
setTimeout(() => {
resolve(1)
}, 2000)
})
.then(val => {
console.log(new Date(), val)
return delayDouble(val, 2000)
})
.then(val => {
console.log(new Date(), val)
})

上面的結(jié)果也是完美符合我們的預(yù)期。
catch 方法
catch 方法其實(shí)很簡(jiǎn)單,相當(dāng)于 then 方法的一個(gè)簡(jiǎn)寫(xiě)。
class Deferred {
constructor(callback) {}
then(onResolve, onReject) {}
catch(onReject) {
return this.then(null, onReject)
}
}
靜態(tài)方法
resolve/reject
Promise 類(lèi)還提供了兩個(gè)靜態(tài)方法,直接返回狀態(tài)已經(jīng)固定的 promise 對(duì)象。
class Deferred {
constructor(callback) {}
then(onResolve, onReject) {}
catch(onReject) {}
static resolve(value) {
return new Deferred((resolve, reject) => {
resolve(value)
})
}
static reject(reason) {
return new Deferred((resolve, reject) => {
reject(reason)
})
}
}
all
all 方法接受一個(gè) promise 對(duì)象的數(shù)組,等數(shù)組中所有的 promise 對(duì)象的狀態(tài)變?yōu)?fulfilled ,然后返回結(jié)果,其結(jié)果也是一個(gè)數(shù)組,數(shù)組的每個(gè)值對(duì)應(yīng)的是 promise 對(duì)象的內(nèi)部結(jié)果。
首先,我們需要先判斷傳入的參數(shù)是否為數(shù)組,然后構(gòu)造一個(gè)結(jié)果數(shù)組以及一個(gè)新的 promise 對(duì)象。
class Deferred {
static all(promises) {
// 非數(shù)組參數(shù),拋出異常
if (!Array.isArray(promises)) {
return Deferred.reject(new TypeError('args must be an array'))
}
// 用于存儲(chǔ)每個(gè) promise 對(duì)象的結(jié)果
const result = []
const length = promises.length
// 如果 remaining 歸零,表示所有 promise 對(duì)象已經(jīng) fulfilled
let remaining = length
const promise = new Deferred(function (resolve, reject) {
// TODO
})
return promise
}
}
接下來(lái),我們需要進(jìn)行一下判斷,對(duì)每個(gè) promise 對(duì)象的 resolve 進(jìn)行攔截,每次 resolve 都需要將 remaining 減一,直到 remaining 歸零。
class Deferred {
static all(promises) {
// 非數(shù)組參數(shù),拋出異常
if (!Array.isArray(promises)) {
return Deferred.reject(new TypeError('args must be an array'))
}
const result = [] // 用于存儲(chǔ)每個(gè) promise 對(duì)象的結(jié)果
const length = promises.length
let remaining = length
const promise = new Deferred(function (resolve, reject) {
// 如果數(shù)組為空,則返回空結(jié)果
if (promises.length === 0) return resolve(result)
function done(index, value) {
doThenFunc(
promise,
value,
(val) => {
// resolve 的結(jié)果放入 result 中
result[index] = val
if (--remaining === 0) {
// 如果所有的 promise 都已經(jīng)返回結(jié)果
// 然后運(yùn)行后面的邏輯
resolve(result)
}
},
reject
)
}
// 放入異步隊(duì)列
setTimeout(() => {
for (let i = 0; i < length; i++) {
done(i, promises[i])
}
})
})
return promise
}
}
下面我們通過(guò)如下代碼,判斷邏輯是否正確。按照預(yù)期,代碼運(yùn)行后,在 3 秒之后,控制臺(tái)會(huì)打印一個(gè)數(shù)組 [2, 4, 6] 。
const delayDouble = (num, time) => new Deferred((resolve) => {
setTimeout(() => {
resolve(2 * num)
}, time)
})
console.log(new Date())
Deferred.all([
delayDouble(1, 1000),
delayDouble(2, 2000),
delayDouble(3, 3000)
]).then((results) => {
console.log(new Date(), results)
})

上面的運(yùn)行結(jié)果,基本符合我們的預(yù)期。
race
race 方法同樣接受一個(gè) promise 對(duì)象的數(shù)組,但是它只需要有一個(gè) promise 變?yōu)?fulfilled 狀態(tài)就會(huì)返回結(jié)果。
class Deferred {
static race(promises) {
if (!Array.isArray(promises)) {
return Deferred.reject(new TypeError('args must be an array'))
}
const length = promises.length
const promise = new Deferred(function (resolve, reject) {
if (promises.length === 0) return resolve([])
function done(value) {
doThenFunc(promise, value, resolve, reject)
}
// 放入異步隊(duì)列
setTimeout(() => {
for (let i = 0; i < length; i++) {
done(promises[i])
}
})
})
return promise
}
}
下面我們將前面驗(yàn)證 all 方法的案例改成 race。按照預(yù)期,代碼運(yùn)行后,在 1 秒之后,控制臺(tái)會(huì)打印一個(gè)2。
const delayDouble = (num, time) => new Deferred((resolve) => {
setTimeout(() => {
resolve(2 * num)
}, time)
})
console.log(new Date())
Deferred.race([
delayDouble(1, 1000),
delayDouble(2, 2000),
delayDouble(3, 3000)
]).then((results) => {
console.log(new Date(), results)
})

上面的運(yùn)行結(jié)果,基本符合我們的預(yù)期。
總結(jié)
一個(gè)簡(jiǎn)易版的 Promise 類(lèi)就已經(jīng)實(shí)現(xiàn)了,這里還是省略了部分細(xì)節(jié),完整代碼可以訪問(wèn) github 。Promise 的出現(xiàn)為后期的 async 語(yǔ)法打下了堅(jiān)實(shí)基礎(chǔ),下一篇博客可以好好聊一聊 JavaScript 的異步編程史,不小心又給自己挖坑了。。。
到此這篇關(guān)于手把手教你實(shí)現(xiàn) Promise的方法的文章就介紹到這了,更多相關(guān)Promise語(yǔ)法內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
淺談Node.js爬蟲(chóng)之網(wǎng)頁(yè)請(qǐng)求模塊
本篇文章主要介紹了淺談Node.js爬蟲(chóng)之網(wǎng)頁(yè)請(qǐng)求模塊,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-01-01
node.js?中的require用法和import的區(qū)別解析
在Node.js中,require是一個(gè)內(nèi)置的函數(shù),用于在當(dāng)前模塊中加載和緩存其他模塊,這篇文章給大家介紹node.js?中的require用法和import的區(qū)別,感興趣的朋友跟隨小編一起看看吧2024-04-04
node.js實(shí)現(xiàn)token身份驗(yàn)證的示例代碼
本文主要介紹了node.js實(shí)現(xiàn)token身份驗(yàn)證的示例代碼,文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-02-02
Node.js模擬發(fā)起http請(qǐng)求從異步轉(zhuǎn)同步的5種用法
這篇文章主要介紹了Node.js模擬發(fā)起http請(qǐng)求從異步轉(zhuǎn)同步的5種方法,下面總結(jié)了幾個(gè)常見(jiàn)的庫(kù) API 從異步轉(zhuǎn)同步的幾種方法。需要的朋友可以參考下2018-09-09
nodejs利用readline提示輸入內(nèi)容實(shí)例代碼
這篇文章主要給大家介紹了關(guān)于nodejs利用readline提示輸入內(nèi)容的相關(guān)資料,Readline是Node.js里實(shí)現(xiàn)標(biāo)準(zhǔn)輸入輸出的封裝好的模塊,通過(guò)這個(gè)模塊我們可以以逐行的方式讀取數(shù)據(jù)流,需要的朋友可以參考下2021-07-07
手把手教你使用TypeScript開(kāi)發(fā)Node.js應(yīng)用
為了減少代碼編寫(xiě)過(guò)程中出現(xiàn)的錯(cuò)誤,以及更好的維護(hù)你的項(xiàng)目,本文將手把手教你配置一個(gè)簡(jiǎn)單的開(kāi)發(fā)環(huán)境來(lái)編寫(xiě)Node.js的應(yīng)用程序,感興趣的小伙伴們可以參考一下2019-05-05
Node.js與MySQL交互操作及其注意事項(xiàng)
這篇文章給大家主要介紹了Node.js與MySQL交互操作及其注意事項(xiàng),非常的詳細(xì),有相同需求的小伙伴可以參考下2016-10-10

