Promise
JavaScript 是屬於同步的程式語言,因此一次僅能做一件事情,但遇到非同步的事件時,就會將非同步的事件移動到程式碼的最後方,等到所有的原始碼運行完以後才會執行非同步的事件。
以下列的程式碼來說,在 console 中依序的會出現的順序為:
開始
結束
非同步事件 <- 最後執行
console.log('開始');
setTimeout(() => {
console.log('非同步事件');
}, 0);
console.log('結束');
雖然在上段的原始碼中,setTimeout
所定義的時間為 0,但因為是屬於非同步事件,因此還是會在其他原始碼運行完以後才執行。
我們再axios
文件中找一段實際範例參考,先定義一個data物件
,中間段落使用 axios
嘗試取得遠端資料,後面的緊接的 console.log(data),實際測試看看console的結果是什麼?
你應該會覺得concole順序依序為:
"開始執行"
'/user?ID=12345
"測試有沒有接收到資料"
let data = {}
console.log('開始執行');
const api = '/user?ID=12345'
axios.get(api).then(response => {
data = response;
console.log('測試有沒有接收到資料');
});
console.log(data);
其實正確的 console 中依序的會出現的順序是:
"開始執行"
[object Object] { ... }
"測試有沒有接收到資料"
你應該會想說為什麼console.log(data)會沒有接收到資料呢?
因為上述的非同步行為,導致api還尚未接收到,因此才會出現[object]
實戰演練Promise,以Vue為範例
一、數字題型:
const app = new Vue({
el: '#app',
created() {
this.promise(100)
.then(success => {
console.log(success);
return this.promise(1000);
})
.then(success => {
console.log(success);
return this.promise(0); // 這個階段會進入 catch
})
.then(success => { // 由於上一個階段結果是 reject,所以此段不執行
console.log(success);
return this.promise('Hello');
})
.catch(error => {
console.log(error);
})
},
methods: {
promise (num) {
return new Promise((resolve, reject) => {
num ? resolve('成功') : reject('失敗');
});
}
},
})
因此console出來的結果為:
成功
成功
失敗
二、JSON Data一般題型:
將題型一的值更改為JSON,分別為apiA
以及apiB
可將此段程式碼複製貼到開發者工具
看有沒有什麼不同的地方~
const app = new Vue({
el: '#app',
created () {
const apiA = 'https://data.taipei/api/v1/dataset/80ab7634-dabd-4012-be8c-fb7da0101e9b?scope=resourceAquire'
const apiB = 'https://data.taipei/api/v1/dataset/36847f3f-deff-4183-a5bb-800737591de5?scope=resourceAquire'
this.demo(apiA).then(res => {
console.log(res)
return this.demo(apiB)
}).then(res => {
console.log(res)
}).catch(err => {
console.log(err)
})
},
methods: {
demo (api) {
return new Promise((resolve, reject) => {
axios.get(api).then((res) => {
const data = res.data.result.results
res.status === 200 ? resolve(data) : reject('error api')
})
})
}
}
})
三、進階題型:
使用indexOf
去尋找哪個api中有符合80ab7634
的字串,有的話則會執行setTimeout
延遲5秒!
然而,在這個範例中indexOf
尋找到apiA
有符合,但是js是由上到下去執行的,因此js會等待apiA
回傳回來後,接著馬上跟著回傳apiB
,因此apiA與apiB
都將延遲5秒回傳
const app = new Vue({
el: '#app',
created() {
const apiA = 'https://data.taipei/api/v1/dataset/80ab7634-dabd-4012-be8c-fb7da0101e9b?scope=resourceAquire'
const apiB = 'https://data.taipei/api/v1/dataset/36847f3f-deff-4183-a5bb-800737591de5?scope=resourceAquire'
this.demo(apiA).then(res => {
console.log(res);
return this.demo(apiB)
}).then(res => {
console.log(res);
}).catch(err => {
console.log(err);
})
},
methods: {
demo(api) {
return new Promise((resolve, reject) => {
axios.get(api).then((res) => {
if (res.status === 200) {
if (api.indexOf('80ab7634') > -1) {
setTimeout(() => {
resolve(res.data)
}, 5000)
} else {
resolve(res.data)
}
} else {
reject('error api')
}
})
})
},
},
})
假如我們將indexOf的值變更為36847f3f
,猜猜看會出現什麼有趣的事呢?
這時會發現,js先回傳apiA
,經過了5秒後才會回傳apiB
因為js未搜尋到apiA有36847f3f
的值,因此不會去執行setTimeOut
const app = new Vue({
el: '#app',
created() {
const apiA = 'https://data.taipei/api/v1/dataset/80ab7634-dabd-4012-be8c-fb7da0101e9b?scope=resourceAquire'
const apiB = 'https://data.taipei/api/v1/dataset/36847f3f-deff-4183-a5bb-800737591de5?scope=resourceAquire'
this.demo(apiA).then(res => {
console.log(res);
return this.demo(apiB)
}).then(res => {
console.log(res);
}).catch(err => {
console.log(err);
})
},
methods: {
demo(api) {
return new Promise((resolve, reject) => {
axios.get(api).then((res) => {
if (res.status === 200) {
if (api.indexOf('36847f3f') > -1) {
setTimeout(() => {
resolve(res.data)
}, 5000)
} else {
resolve(res.data)
}
} else {
reject('error api')
}
})
})
},
},
})
希望藉由上述三個不同的簡單實例,能讓大家更了解Promise的運作模式
感謝分享
補充 new Vue() 是 Vue 2 的語法,
Vue 3 用 Vue.createApp() 取代 new Vue()
https://book.vue.tw/appendix/migration.html#%E5%85%83%E4%BB%B6%E5%AF%A6%E9%AB%94%E5%BB%BA%E7%AB%8B
Vue 2 support will end on Dec 31, 2023. Learn more about Vue 2 Extended LTS.
The Benefits of the New Vue 3 App Initialization Code