iT邦幫忙

2023 iThome 鐵人賽

DAY 7
0
Vue.js

業主說給你30天學會Vue系列 第 7

V07_在vue之前_必備的JS基礎(4)_同步與非同步的操作

  • 分享至 

  • xImage
  •  

V07_在vue之前_必備的JS基礎(4)_同步與非同步的操作

Promise 是一個因應非同步操作的需求所產生的語法
可以參考MDN有關Promise的說明
https://developer.mozilla.org/zh-TW/docs/Web/JavaScript/Reference/Global_Objects/Promise
在MDN產的介紹提到
Promise 物件代表一個即將完成、或失敗的非同步操作,以及它所產生的值。

Promise

Promise是處理非同步的操作,
在原本處理非同步操作的作法,以jQuery為例

<div id="result"></div>

<script>
$("#result").load("ajax/test.html", () => {
  console.log( "Load was performed." );
});
</script>

由以上可看出
在執行完 $("#result").load() 的動作後,成功的話接著會執行
這個程式
() => { console.log( "Load was performed." ); }
也就是所謂的callback的處理

如果有些操作需要等待資料載入完成才會做下一個動作時
就會形成巢狀式的等待, 如以下程式

<script>
$("#div1").load("test1.html", () => {
  console.log( "Load1 was performed." );

  $("#div2").load("test2.html", () => {
	  console.log( "Load2 was performed." );

	  $("#div3").load("test3.html", () => {
		  console.log( "Load3 was performed." );

	  });
  });
});
</script>

或是形成以下的程式碼

<script>
$("#div1").load("test1.html", () => {
  console.log( "Load1 was performed." );
  act2();
});

function act2(){
	$("#div2").load("test2.html", () => {
	  console.log( "Load2 was performed." );
	  act3();
	});
}

function act3(){
	$("#div3").load("test3.html", () => {
	  console.log( "Load3 was performed." );
	});
}
</script>

舉以上的例子,在於理解原本的作法
在改成Promise後,產生什麼樣的變化

基本上Promise要處理的就是 執行的成功處理,失敗處理,以及往下傳遞的處理
透過實際的程式練習,就可以找出理解的盲點及解決的方法

let p1 = new Promise( (resolve, reject) => {

  if(Math.random()<0.5){
    console.log("OK1 Success!");
    resolve("Success!");
  } else {
    console.log("ERR1 Error!");
    reject ("Error!");
  }

}).then(
  (value) => {
    console.log("OK2 "+ value);
    return value;
  },
  (reason) => {
    console.log("ERR2 "+reason); 
    throw reason;
  }

).then(
  (value) => {
    console.log("OK3 "+ value); 
  },
  (reason) => {
    console.log("ERR3 "+reason); 
  }
);

Promise(resolve, reject) : 成功 resolve(value),失敗 reject(reason)
then(value, reason) : 成功 return value,失敗 throw reason

在 Promise 區段 執行完,若要往下執行,利用 成功 resolve(value),失敗 reject(reason)
resolve(value)就相當於 callback,往下執行 then(),
同時按照then()的順序執行

let p2 = new Promise( (resolve, reject) => {

  $("#div1").load("test1.html", () => {
    console.log( "Load1 was performed." );
    resolve("Success!");
  });

}).then(
  (value) => {
    setTimeout(() => {
      $("#div2").load("test2.html", () => {
        console.log( "Load22 was performed.");
      });
    }, 2000);
    console.log( "Load21 was performed.");
    return value;
  }
).then(
  (value) => {
    $("#div3").load("test3.html", () => {
      console.log( "Load3 was performed.");
    });
  }
);

由上面的程式可以發現執行的順序是

Load1 -> Load21 -> Load3 -> Load22
在 Load22 中,加了一個 Timeout 的處理,會延遲2秒執行

在此有一個盲點就是 程序上是 Load1 -> Load21 -> Load3 的順序
並不會等待 Timeout 處理完才走到 下一步

如果想再等 Timeout 處理完再走到下一步,就要使用另一個用法了
就是 async 與 await

Promise可以看作是控制執行的流程

//-----------------

async 與 await

接著就來研究一下 async 與 await

為了解決 需要等待上一個動作完成,才能往下執行的需求
避免 巢狀callback的問題

就要透過 async 與 await 的語法來解決了
有關async 與 await的說明,可以參考MDN的介紹

async function
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function

await
https://developer.mozilla.org/zh-TW/docs/Web/JavaScript/Reference/Operators/await

先來看一下範例
以下是沒有 await 的狀況

function resolveAfter2Seconds() {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve('resolved');
    }, 2000);
  });
}

async function asyncCall() {
  console.log('calling');
  const result = resolveAfter2Seconds();
  console.log(result);
  // Expected output: "resolved"
}

asyncCall();

執行結果

k1: calling
k3: [object Promise]
k2: timeout

以下是有 await 的狀況

function resolveAfter2Seconds() {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve('resolved');
    }, 2000);
  });
}

async function asyncCall() {
  console.log('calling');
  const result = await resolveAfter2Seconds();
  console.log(result);
  // Expected output: "resolved"
}

asyncCall();

執行結果

k1: calling
k2: timeout
k3: resolved

在Promise中加上then()的狀況

function resolveAfter2Seconds() {
  return new Promise((resolve) => {
    setTimeout(() => {
      console.log("k21: timeout");
      resolve('resolved');
    }, 2000);
  }).then(
    (value) => { 
      console.log("k22: then "+value);
      return 123; 
    }
  );
}

async function asyncCall() {
  console.log('k1: calling');
  const result = await resolveAfter2Seconds();
  console.log("k3: "+result);
}

asyncCall();

執行結果

k1: calling
k21: timeout
k22: then resolved
k3: 123

整理如下

function resolveAfter2Seconds() {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve('resolved');
    }, 2000);
  }).then(
    (value) => { 
      return 123; 
    }
  );
}

async function asyncCall() {
  const result = await resolveAfter2Seconds();
}

asyncCall();
async asyncCall() 
-> await resolveAfter2Seconds() 
-> return new Promise( (resolve) => { resolve('resolved'); })
                .then( (value) => { return 123; });
-> result

再整理一下流程

async asyncCall() 
-> await resolveAfter2Seconds() 
-> return Promise() 
-> resolve(value) 
-> result = value;
Promise, async, await 的整合操作的順序
1. 建立 async 的 function
2. 設定 要 await 的 function 及回傳值
3. 透過 await 的 function 的return new Promise()的方式 執行要 await 的項目
4. 透過 Promise 的 resolve(value); 傳回值 value 給then()
5. 透過 then() 的 return value 傳回值 value 給變數 result 

簡單的來說就是
執行 async 及 await,等待 Promise 的 resolve 的回傳值

沒有await的, 即時回傳 function 的return 值
有 await的,會等待 Promise 的 resolve 的回傳值

熟練以上的基本步驟後,再應用到各種不同的狀況

以下是一些不同的寫法範例

async function foo() {
  const result1 = await new Promise((resolve) =>
    setTimeout(() => resolve("1")),
  );
  const result2 = await new Promise((resolve) =>
    setTimeout(() => resolve("2")),
  );
}
foo();

直接new Promise() 的 resolve 回應給 await

function resolveAfter2Seconds() {
  console.log("starting slow promise");
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve("slow");
      console.log("slow promise is done");
    }, 2000);
  });
}

function resolveAfter1Second() {
  console.log("starting fast promise");
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve("fast");
      console.log("fast promise is done");
    }, 1000);
  });
}

async function concurrent2() {
  console.log("== concurrent2 starts ==");

  await Promise.all([
    (async () => console.log(await resolveAfter2Seconds()))(),
    (async () => console.log(await resolveAfter1Second()))(),
  ]);
  console.log("== concurrent2 done ==");
}
concurrent2();

使用 Promise.all() 來同時執行多個await function

以下是最精簡的寫法

(async () => { 
  console.log( await new Promise( (resolve) => resolve(1) )) 
})();

( () => {
  console.log("OK");
} )();

(async () => { 
  console.log( await Promise.resolve(2) );
})();

( () => { "OK2" } )();

上一篇
V06_在Vue之前_必備的JS基礎(3)_DOM元件及樣版文字的操作
下一篇
V08_在vue之前_必備的CSS基礎(1)_動態控制
系列文
業主說給你30天學會Vue31
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言