Promise 的小技巧

在巴哈姆特上學到的。

原理簡述

其實原理應該就是 async await 的語法,只要建立一個 Promise 就可以用 await 來強制等 Promise 執行完才執行後面的程式碼,換句話說就是強制把「非同步 -> 同步」的感覺吧?

這一塊我還沒學到,所以只是我大概瞎猜的,但沒關係就先記下來吧!

備註:await 必須包在 async 開頭的 function 裡面才能用,意思就是「這個 function 裡有非同步 function 的意思吧!」。

備註:await 後面只要接 Promise,就會等這個 Promise 執行 resolvereject 完才會往下執行。

備註:我很常會把 resolve 放錯地方。記住一個要訣:resolve 一定要放在非同步執行完的那個「 callback」裡面呼叫,這樣子才會等非同步執行後再往下跑。

補充-async 不是你想的那樣

注意 async 指的是「裡面」有非同步 function,不是「function 自己」是非同步函式,把下面兩段拿去跑你就懂了。

不是非同步:

1
2
3
4
5
6
7
8
9
async function fakeAsync() {
console.log('fake asnyc!!!')
}
for(let i=0; i<=100; i++) {
// 如果這個真的是非同步
fakeAsync()
// 那這行就應該要比上面的先執行
console.log('sync!! \n')
}

是非同步:

1
2
3
4
5
6
7
8
9
async function asyncFunction() {
console.log('asnyc!!!')
}
for(let i=0; i<=100; i++) {
// 用 setTimeout 變非同步
setTimeout(asyncFunction, 0)
// 所以這行會先執行
console.log('sync!! \n')
}

範例一 更新變數的值

一般的情況:

1
2
3
4
5
6
7
8
9
~async function() {
let a = 10
setTimeout(function() {
a = 100
}, 1000)
// 上面是非同步,不會等上面執行完才執行
// 所以 a = 10
console.log(a)
}()

利用 Promise + await

1
2
3
4
5
6
7
8
9
10
11
12
13
~async function() {
let a = 10
// 等 Promise 執行完才執行後面的程式碼
await new Promise((resolve, reject) => {
setTimeout(function() {
// 更新 a = 100
a = 100
// 這執行完後才往下執行
resolve()
}, 1000)
})
console.log(a)
}()

範例二 讓迴圈的速度變成 LKK

年輕版(錯誤示範):

1
2
3
4
5
6
7
8
9
10
~async function() {
// 這邊故意用 var 不用 let
for (var i=1; i<=5; i++) {
// 經典的閉包題目
// 一秒後會印出 5 個 6
setTimeout(function() {
console.log(i)
}, 1000)
}
}()

老年版:

1
2
3
4
5
6
7
8
9
10
11
12
~async function() {
for (let i=0; i<=10; i++) {
// 每一圈都要先等一秒
await new Promise((resolve, reject) => {
setTimeout(function() {
resolve()
}, 1000)
})
// 才會執行這裡
console.log(i)
}
}()

範例三 發送 request

傳說中的 callback hell:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const request = require('request')
request('https://reqres.in/api/users/1', function(err, res, body) {
console.log('拿到第一筆資料了!', JSON.parse(body))
// call 下一筆 API
request('https://reqres.in/api/users/2', function(err, res, body) {
console.log('拿到第二筆資料了!', JSON.parse(body))
// call 下一筆 API
request('https://reqres.in/api/users/3', function(err, res, body) {
console.log('拿到第三筆資料了!', JSON.parse(body))
// call 下一筆 API
request('https://reqres.in/api/users/4', function(err, res, body) {
console.log('拿到第四筆資料了!', JSON.parse(body))
})
})
})
})

改用 Promise + async

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
~async function() {
// API 1
await new Promise((resolve, reject) => {
request('https://reqres.in/api/users/1', (err, res, body) => {
console.log('拿到第一筆資料了!', JSON.parse(body))
// 往下執行
resolve()
})
})
// API 2
await new Promise((resolve, reject) => {
request('https://reqres.in/api/users/2', (err, res, body) => {
console.log('拿到第二筆資料了!', JSON.parse(body))
// 往下執行
resolve()
})
})
// API 3
await new Promise((resolve, reject) => {
request('https://reqres.in/api/users/3', (err, res, body) => {
console.log('拿到第三筆資料了!', JSON.parse(body))
// 往下執行
resolve()
})
})
// API 4
await new Promise((resolve, reject) => {
request('https://reqres.in/api/users/4', (err, res, body) => {
console.log('拿到第四筆資料了!', JSON.parse(body))
// 往下執行
resolve()
})
})
}()

同場加映 Promise 的原本用法

暫時先放這吧!不然我也不知道該放哪才好~

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// 根據 id 取得 user 資料
function getUser(id) {
return new Promise((resolve, reject) => {
request(`https://reqres.in/api/users/${id}`, (err, res, body) => {
// 成功拿到資料
if (body) {
// 就把資料帶回去
resolve(body)
} else {
// 失敗就帶錯誤回去
reject(err)
}
})
})
}
// 從第一筆開始發
getUser(1)
.then((data) => {
console.log('第一筆資料!' ,JSON.parse(data))
return getUser(2)
})
.then((data) => {
console.log('第二筆資料!', JSON.parse(data))
return getUser(3)
})
.then((data) => {
console.log('第三筆資料!', JSON.parse(data))
})
.catch((err) => console.log('err:', err))
.catch((err) => console.log('err:', err))
.catch((err) => console.log('err:', err))

同場加映 async 的原本用法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
// 根據 id 取得 user 資料
function getUser(id) {
return new Promise((resolve, reject) => {
request(`https://reqres.in/api/users/${id}`, (err, res, body) => {
// 成功拿到資料
if (body) {
// 就把資料帶回去
resolve(body)
} else {
// 失敗就帶錯誤回去
reject(err)
}
})
})
}
~async function() {
/*
依序取得 user1, user2, user3,
加 {} 是為了建立區塊作用域,
不然沒辦法重複用 data 來解構
*/
{
const json = await getUser(1)
const { data } = JSON.parse(json)
console.log(data)
}
{
const json = await getUser(2)
const { data } = JSON.parse(json)
console.log(data)
}
{
const json = await getUser(3)
const { data } = JSON.parse(json)
console.log(data)
}
}()

或搭配迴圈寫的更簡潔:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 根據 id 取得 user 資料
function getUser(id) {
return new Promise((resolve, reject) => {
request(`https://reqres.in/api/users/${id}`, (err, res, body) => {
// 成功拿到資料
if (body) {
// 就把資料帶回去
resolve(body)
} else {
// 失敗就帶錯誤回去
reject(err)
}
})
})
}
~async function() {
for (let i=1; i<=3; i++) {
// 每圈都會先等這行執行完才往下執行
const json = await getUser(i)
const { data } = JSON.parse(json)
console.log(i, data)
}
}()

同場加映 async 的回傳值也是一個 Promise

只要回傳的是 Promise,就可以一直 then 下去:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
async function user1() {
const json = await getUser(1)
console.log({ data } = JSON.parse(json))
}
/*
先抓第一筆 user,接著
.then: 抓第二筆,注意裡面也是 async 函式,因此最後回傳 Promise
.then: 抓第三筆,同上
*/
user1()
.then(async() => {
const json = await getUser(2)
console.log({ data } = JSON.parse(json))
})
.then(async() => {
const json = await getUser(3)
console.log({ data } = JSON.parse(json))
})

同場加映 比 setTimeout 難一點的 setInterval

希望你能成為非同步操控大師:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
function count(msg, ms) {
return new Promise((resolve, reject) => {
// 計數器
let counter = 0
// 建立計數器
let timer = setInterval(() => {
/*
callback 流程
1. 先把 counter + 1
2. 印出輸入的文字 + counter
3. 檢查 counter 值,超過 5 就移除計數器
*/
counter++
console.log(`${msg}: ${counter}`)
if (counter === 5) {
// 清除計時器
clearInterval(timer)
// 等計數器清除後,才執行 resolve
resolve()
}
}, ms)
})
}

~async function() {
console.log('1')
// 等到 resolve 被執行後才會往下跑
await count('yo', 100)
console.log('2')
}()

耍帥的寫法(其實跟上面一模一樣):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
~async function() {
console.log(1)
await new Promise((resolve, reject) => {
// IIFE
~function(msg, ms) {
let counter = 0
const timer = setInterval(() => {
counter++
console.log(`${msg}: ${counter}`)
if (counter === 5) {
clearInterval(timer)
resolve()
}
}, ms)
}('yo', 100)
// IIFE END
})
console.log(2)
}()

同場加映 更好懂的排序方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
const request = require('request')
async function getUser(id) {
// 儲存 response
let json = null
await new Promise((resolve) => {
request({
url: `https://reqres.in/api/users/${id}`,
}, (err, res, body) => {
// 拿到 response
json = body
// 才往下執行
resolve()
})
})
// 回傳拿到的資料
return json
}

function getAllUsers(user1, user2, user3) {
console.log('user1', JSON.parse(user1))
console.log('user2', JSON.parse(user2))
console.log('user3', JSON.parse(user3))
}

/* 主要的 function */
~async function() {
// 按照順序取得 1, 2, 3 的資料
const user1 = await getUser(1)
const user2 = await getUser(2)
const user3 = await getUser(3)
// 拿到之後再丟給要處理的 function
getAllUsers(user1, user2, user3)
}()

取得所有資料 Promise.All

當你想一次打多個 API 時,應該就會用到這個方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// 可以改成 false 來測試
let data = { name: 'jim', email: 'jimjim@gmail.com' }

const youtubeAPI = new Promise(resolve => {
setTimeout(() => {
resolve({ video: [1, 2, 3, 4, 5] })
}, 3000)
})

const facebookAPI = new Promise(resolve => {
setTimeout(() => {
resolve({ user: 'name' })
}, 1000)
})


const checkPermission = new Promise((resolve, reject) => {
setTimeout(() => {
data ? resolve(data) : reject(`We don't find the data.`)
}, 2000)
})


Promise.all([youtubeAPI, facebookAPI, checkPermission])
.then(result => {
// 拿到所有結果
console.log(result[0])
console.log(result[1])
console.log(result[2])
}).catch(err => {
// 只要其中一個出錯,就跳到這裡
console.log(err);
})
mentor-program-day41 執行 IIFE 的三種方式
Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×