乐趣区

关于javascript:20分钟带你掌握JavaScript-Promise和-AsyncAwait

个别在开发中,查问网络 API 操作时往往是比拟耗时的,这意味着可能须要一段时间的期待能力取得响应。因而,为了防止程序在申请时无响应的状况,异步编程就成为了开发人员的一项基本技能。

在 JavaScript 中解决异步操作时,通常咱们常常会听到 “Promise “ 这个概念。但要了解它的工作原理及应用办法可能会比拟形象和难以了解。

那么,在本文中咱们将会通过实际的形式让你能更疾速的了解它们的概念和用法,所以与许多传统水灵灵的教程都不同,咱们将通过以下四个示例开始:

  • 示例 1:用生日解释 Promise 的基础知识
  • 示例 2:一个猜数字的游戏
  • 示例 3:从 Web API 中获取国家信息
  • 示例 4:从 Web API 中获取一个国家的周边国家列表

示例 1:用生日解释 Promise 基础知识

首先,咱们先来看看 Promise 的根本状态是什么样的。

Promise 执行时候三个状态:pending(执行中)、fulfilled(胜利)、rejected(失败)。

new Promise(function(resolve, reject) {if (/* 异步操作胜利 */) {resolve(value); // 将 Promise 的状态由 padding 改为 fulfilled
    } else {reject(error); // 将 Promise 的状态由 padding 改为 rejected
    }
})

实现时有三个原型办法 then、catch、finally

promise
.then((result) => {//promise 被接管或回绝继续执行的状况})
.catch((error) => {//promise 被回绝的状况})
.finally (() => {//promise 实现时,无论如何都会执行的状况})

根本状态介绍实现了,那么咱们上面开始看看上面的示例吧。

用户故事:我的敌人 Kayo 许可在两周后在我的生日 Party 上为我做一个蛋糕。

如果一切顺利且 Kayo 没有生病的话,咱们就会取得肯定数量的蛋糕,但如果 Kayo 生病了,咱们就没有蛋糕了。但不管有没有蛋糕,咱们依然会开一个生日 Party。

所以对于这个示例,咱们将如上的背景故事翻译成 JS 代码,首先让咱们先创立一个返回 Promise 的函数。

const onMyBirthday = (isKayoSick) => {return new Promise((resolve, reject) => {setTimeout(() => {if (!isKayoSick) {resolve(2);
      } else {reject(new Error("I am sad"));
      }
    }, 2000);
  });
};

在 JavaScript 中,咱们能够应用 new Promise() 创立一个新的 Promise,它承受一个参数为:(resolve,reject)=>{} 的函数。

在此函数中,resolve 和 reject 是默认提供的回调函数。让咱们认真看看下面的代码。

当咱们运行 onMyBirthday 函数 2000ms 后。

  • 如果 Kayo 没有生病,那么咱们就以 2 为参数执行 resolve 函数
  • 如果 Kayo 生病了,那么咱们用 new Error(“I am sad”) 作为参数执行 reject。只管您能够将任何要回绝的内容作为参数传递,但倡议将其传递给 Error 对象。

当初,因为 onMyBirthday() 返回的是一个 Promise,咱们能够拜访 then、catch 和 finally 办法。咱们还能够拜访早些时候在 then 和 catch 中应用传递给 resolve 和 reject 的参数。

让咱们通过如下代码来了解概念

如果 Kayo 没有生病

onMyBirthday(false)
  .then((result) => {console.log(\`I have ${result} cakes\`); // 控制台打印“I have 2 cakes”})
  .catch((error) => {console.log(error); // 不执行
  })
  .finally(() => {console.log("Party"); // 控制台打印“Party”});

如果 Kayo 生病

onMyBirthday(true)
  .then((result) => {console.log(\`I have ${result} cakes\`); // 不执行 
  })
  .catch((error) => {console.log(error); // 控制台打印“我很惆怅”})
  .finally(() => {console.log("Party"); // 控制台打印“Party”});

置信通过这个例子你能理解 Promise 的基本概念。

上面咱们开始示例 2

示例 2:一个猜数字的游戏

根本需要:

  • 用户能够输出任意数字
  • 零碎从 1 到 6 中随机生成一个数字
  • 如果用户输出数字等于零碎随机数,则给用户 2 分
  • 如果用户输出数字与零碎随机数相差 1,给用户 1 分,否则,给用户 0 分
  • 用户想玩多久就玩多久

对于下面的需要,咱们首先创立一个 enterNumber 函数并返回一个 Promise:

const enterNumber = () => {return new Promise((resolve, reject) => {// 从这开始编码});
};

咱们要做的第一件事是向用户索要一个数字,并在 1 到 6 之间随机抉择一个数字:

const enterNumber = () => {return new Promise((resolve, reject) => {const userNumber = Number(window.prompt("Enter a number (1 - 6):")); // 向用户索要一个数字
    const randomNumber = Math.floor(Math.random() * 6 + 1); // 抉择一个从 1 到 6 的随机数
  });
};

当用户输出一个不是数字的值。这种状况下,咱们调用 reject 函数,并抛出谬误:

const enterNumber = () => {return new Promise((resolve, reject) => {const userNumber = Number(window.prompt("Enter a number (1 - 6):")); // 向用户索要一个数字
    const randomNumber = Math.floor(Math.random() * 6 + 1); // 抉择一个从 1 到 6 的随机数

    if (isNaN(userNumber)) {reject(new Error("Wrong Input Type")); // 当用户输出的值非数字,抛出异样并调用 reject 函数
    }
  });
};

上面,咱们须要查看 userNumber 是否等于 RanomNumber,如果相等,咱们给用户 2 分,而后咱们能够执行 resolve 函数来传递一个 object {points: 2, randomNumber} 对象。

如果 userNumber 与 randomNumber 相差 1,那么咱们给用户 1 分。否则,咱们给用户 0 分。

return new Promise((resolve, reject) => {const userNumber = Number(window.prompt("Enter a number (1 - 6):")); // 向用户索要一个数字
  const randomNumber = Math.floor(Math.random() * 6 + 1); // 抉择一个从 1 到 6 的随机数

  if (isNaN(userNumber)) {reject(new Error("Wrong Input Type")); // 当用户输出的值非数字,抛出异样并调用 reject 函数
  }

  if (userNumber === randomNumber) {
    // 如果相等,咱们给用户 2 分
    resolve({
      points: 2,
      randomNumber,
    });
  } else if (
    userNumber === randomNumber - 1 ||
    userNumber === randomNumber + 1
  ) {
    // 如果 userNumber 与 randomNumber 相差 1,那么咱们给用户 1 分
    resolve({
      points: 1,
      randomNumber,
    });
  } else {
    // 否则用户得 0 分
    resolve({
      points: 0,
      randomNumber,
    });
  }
});

上面,让咱们再创立一个函数来询问用户是否想持续游戏:

const continueGame = () => {return new Promise((resolve) => {if (window.confirm("Do you want to continue?")) { // 向用户询问是否要持续游戏
      resolve(true);
    } else {resolve(false);
    }
  });
};

为了不使游戏强制完结,咱们创立的 Promise 没有应用 Reject 回调。

上面,咱们创立一个函数来解决猜数字逻辑:

const handleGuess = () => {enterNumber() // 返回一个 Promise 对象
    .then((result) => {alert(\`Dice: ${result.randomNumber}: you got ${result.points} points\`); // 当 resolve 运行时,咱们失去用户得分和随机数 
      
      // 向用户询问是否要持续游戏
      continueGame().then((result) => {if (result) {handleGuess(); // If yes, 游戏持续
        } else {alert("Game ends"); // If no, 弹出游戏完结框
        }
      });
    })
    .catch((error) => alert(error));
};

handleGuess(); // 执行 handleGuess 函数 

在这当咱们调用 handleGuess 函数时,enterNumber() 返回一个 Promise 对象。

如果 Promise 状态为 resolved,咱们就调用 then 办法,向用户告知竞猜后果与得分,并向用户询问是否要持续游戏。

如果 Promise 状态为 rejected,咱们将显示一条用户输出谬误的信息。

不过,这样的代码尽管能解决问题,但读起来还是有点艰难。让咱们前面将应用 async/await 对 hanldeGuess 进行重构。

网上对于 async/await 的解释曾经很多了,在这我想用一个简略概括的说法来解释:async/await 就是能够把简单难懂的异步代码变成类同步语法的语法糖

上面开始看重构后代码吧:

const handleGuess = async () => {
  try {const result = await enterNumber(); // 代替 then 办法,咱们只需将 await 放在 promise 前,就能够间接取得后果

    alert(\`Dice: ${result.randomNumber}: you got ${result.points} points\`);

    const isContinuing = await continueGame();

    if (isContinuing) {handleGuess();
    } else {alert("Game ends");
    }
  } catch (error) { // catch 办法能够由 try, catch 函数来代替
    alert(error);
  }
};

通过在函数前应用 async 关键字,咱们创立了一个异步函数,在函数内的应用办法较之前有如下不同:

  • 和 then 函数不同,咱们只需将 await 关键字放在 Promise 前,就能够间接取得后果。
  • 咱们能够应用 try, catch 语法来代替 promise 中的 catch 办法。

上面是咱们重构后的残缺代码,供参考:

const enterNumber = () => {return new Promise((resolve, reject) => {const userNumber = Number(window.prompt("Enter a number (1 - 6):")); // 向用户索要一个数字
    const randomNumber = Math.floor(Math.random() * 6 + 1); // 零碎随机选取一个 1 - 6 的数字

    if (isNaN(userNumber)) {reject(new Error("Wrong Input Type")); // 如果用户输出非数字抛出谬误
    }

    if (userNumber === randomNumber) { // 如果用户猜数字正确,给用户 2 分
      resolve({
        points: 2,
        randomNumber,
      });
    } else if (
      userNumber === randomNumber - 1 ||
      userNumber === randomNumber + 1
    ) { // 如果 userNumber 与 randomNumber 相差 1,那么咱们给用户 1 分
      resolve({
        points: 1,
        randomNumber,
      });
    } else { // 不正确,得 0 分
      resolve({
        points: 0,
        randomNumber,
      });
    }
  });
};

const continueGame = () => {return new Promise((resolve) => {if (window.confirm("Do you want to continue?")) { // 向用户询问是否要持续游戏
      resolve(true);
    } else {resolve(false);
    }
  });
};

const handleGuess = async () => {
  try {const result = await enterNumber(); // await 代替了 then 函数

    alert(\`Dice: ${result.randomNumber}: you got ${result.points} points\`);

    const isContinuing = await continueGame();

    if (isContinuing) {handleGuess();
    } else {alert("Game ends");
    }
  } catch (error) { // catch 办法能够由 try, catch 函数来代替
    alert(error);
  }
};

handleGuess(); // 执行 handleGuess 函数 

咱们曾经实现了第二个示例,接下来让咱们开始看看第三个示例。

示例 3:从 Web API 中获取国家信息

个别当从 API 中获取数据时,开发人员会精彩应用 Promises。如果在新窗口关上 https://restcountries.eu/rest…,你会看到 JSON 格局的国家数据。

通过应用 Fetch API,咱们能够很轻松的取得数据,以下是代码:

const fetchData = async () => {const res = await fetch("https://restcountries.eu/rest/v2/alpha/cn"); // fetch() returns a promise, so we need to wait for it

  const country = await res.json(); // res is now only an HTTP response, so we need to call res.json()

  console.log(country); // China's data will be logged to the dev console
};

fetchData();

当初咱们取得了所需的国家 / 地区数据,让咱们转到最初一项工作。

示例 4:从 Web API 中获取一个国家的周边国家列表

上面的 fetchCountry 函数从示例 3 中的 api 取得国家信息,其中的参数 alpha3Code 是代指该国家的国家代码,以下是代码

// Task 4: 取得中国周边的邻国信息
const fetchCountry = async (alpha3Code) => {
  try {
    const res = await fetch(\`https://restcountries.eu/rest/v2/alpha/${alpha3Code}\`
    );

    const data = await res.json();

    return data;
  } catch (error) {console.log(error);
  }
};

上面让咱们创立一个 fetchCountryAndNeighbors 函数,通过传递 cn 作为 alpha3code 来获取中国的信息。

const fetchCountryAndNeighbors = async () => {const china= await fetchCountry("cn");

  console.log(china);
};

fetchCountryAndNeighbors();

在控制台中,咱们看看对象内容:

在对象中,有一个 border 属性,它是中国周边邻国的 alpha3codes 列表。

当初,如果咱们尝试通过以下形式获取邻国信息。

const neighbors = 
    china.borders.map((border) => fetchCountry(border));

neighbors 是一个 Promise 对象的数组。

当解决一个数组的 Promise 时,咱们须要应用 Promise.all。

const fetchCountryAndNeigbors = async () => {const china = await fetchCountry("cn");

  const neighbors = await Promise.all(china.borders.map((border) => fetchCountry(border))
  );

  console.log(neighbors);
};

fetchCountryAndNeigbors();

在控制台中,咱们应该可能看到国家 / 地区对象列表。

以下是示例 4 的所有代码,供您参考:

const fetchCountry = async (alpha3Code) => {
  try {
    const res = await fetch(\`https://restcountries.eu/rest/v2/alpha/${alpha3Code}\`
    );
    const data = await res.json();
    return data;
  } catch (error) {console.log(error);
  }
};

const fetchCountryAndNeigbors = async () => {const china = await fetchCountry("cn");
  const neighbors = await Promise.all(china.borders.map((border) => fetchCountry(border))
  );
  console.log(neighbors);
};

fetchCountryAndNeigbors();

总结

实现这 4 个示例后,你能够看到 Promise 在解决异步操作或不是同时产生的事件时很有用。置信在一直的实际中,对它的了解会越深、越强,心愿这篇文章能对大家了解 Promise 和 Async/Await 带来一些帮忙。

以下是本文中应用的代码:

Promise-Async-Await-main.zip

退出移动版