回调函数
最早解决异步的方式就是使用回调函数,简单理解就是一个函数被作为参数传递给另一个函数。
回调函数跟异步没有必然联系,只能说回调函数是解决异步的方法之一。
示例
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 38 39 40 41 42 43
|
function getData(callback) { setTimeout(() => { console.log("获取数据"); callback("data"); }, 1000); }
function processData(data, callback) { setTimeout(() => { console.log("处理数据" + data); callback(data.toUpperCase()); }, 1000); }
function displayData(data, callback) { setTimeout(() => { console.log("显示数据" + data); callback(); }, 1000); }
getData(data => { processData(data, processedData => { displayData(processedData, () => { console.log("操作完成"); }); }); });
|
需要注意的是,回调虽简单,但当有多个异步操作需要串行执行或者有多个异步操作之间存在依赖关系时,嵌套使用回调函数会导致回调地狱,使得代码难以维护。
1 2 3 4 5 6 7 8 9 10 11
| a(() => { b(() => { c(() => { d(() => { e(() => { f(...) }) }) }) }) })
|
总结:
Promise
Promise是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强大。
它由社区最早提出和实现,ES6 将其写进了语言标准,统一了用法,原生提供了Promise对象。
详细了解Promise可以看我之前的文章:
异步编程怎么搞,Promise 知多少? | 灰太羊的羊村 (huitaiyang.top)
示例
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 38 39 40 41 42 43 44 45
|
function getData() { return new Promise((resolve, reject) => { setTimeout(() => { console.log("获取数据"); resolve("data"); }, 1000); }); }
function processData(data) { return new Promise((resolve, reject) => { setTimeout(() => { console.log("处理数据" + data); resolve(data.toUpperCase()); }, 1000); }); }
function displayData(data) { return new Promise((resolve, reject) => { setTimeout(() => { console.log("显示数据" + data); resolve(); }, 1000); }); }
getData() .then(data => processData(data)) .then(data => displayData(data)) .then(() => console.log("操作完成"));
|
总结
- 优点:解决了回调地狱;
catch()
方法处理错误;支持链式调用then()
- 缺点:一旦创建无法取消,浪费资源;错误处理不够灵活
Generator函数
Generator 函数是 ES6 提供的一种异步编程解决方案。
Generator 函数与普通函数不同之处在于:
function
与函数名之间有个*
- Generator 函数调用得到一个生成器对象,具有可迭代属性,调用
next()
方法继续往后执行,碰到yield
就暂停
详细了解Generator可以看我之前的文章:
听说你还没听过 ES6 的 Generator 函数? | 灰太羊的羊村 (huitaiyang.top)
示例
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 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69
| function getData(){ return new Promise((resolve, reject) => { setTimeout(() => { console.log("获取数据"); resolve("data"); }, 1000); }); }
function processData(data){ return new Promise((resolve, reject) => { setTimeout(() => { console.log("处理数据" + data); resolve(data.toUpperCase()); }, 1000); }); }
function displayData(data){ return new Promise((resolve, reject) => { setTimeout(() => { console.log("显示数据" + data); resolve(); }, 1000); }); }
function* gen(){ const data = yield getData(); const processedData = yield processData(data); return displayData(processedData); }
function run(generator){ const g = generator(); function next(data){ const result = g.next(data); if(result.done) return; result.value.then(data => { next(data); }); } next(); }
run(gen);
|
总结
- 优点:可以分段执行,可以暂停;可以控制每个阶段的返回值;可以知道是否执行完毕;借助 co 模块处理异步
- 缺点:语法复杂,学习成本高;使用迭代器执行,调试困难
async/await
ES2017 标准引入了 async
函数,使得异步操作变得更加方便。一句话,async
函数是 Generator 函数的语法糖。
async 用于声明一个 function 是异步的,await 用于等待一个异步方法执行完成
async
函数返回的是一个 Promise 对象,如果在 async
函数中直接 return
一个直接量,async
会把这个直接量通过 PromIse.resolve()
封装成Promise对象返回
await
只能在 async
函数中使用
await
后面不是Promise对象,直接执行
await
后面是Promise对象会阻塞后面的代码,Promise对象 resolve
,然后得到 ****resolve
的值,作为 await
表达式的运算结果
详细了解async/await
可以看我之前的文章:
现代化的异步编程方式 ——async /await | 灰太羊的羊村 (huitaiyang.top)
示例
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 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85
| function getData(){ return new Promise((resolve, reject) => { setTimeout(() => { console.log("获取数据"); resolve("data"); }, 1000); }); }
function processData(data){ return new Promise((resolve, reject) => { setTimeout(() => { console.log("处理数据" + data); resolve(data.toUpperCase()); }, 1000); }); }
function displayData(data){ return new Promise((resolve, reject) => { setTimeout(() => { console.log("显示数据" + data); resolve(); }, 1000); }); }
async function main(){ try { const data = await getData(); const processedData = await processData(data); await displayData(processedData); console.log("操作完成"); } catch(error) { console.log(error); } }
console.log("我的位置在main之前");
main();
console.log("我的位置在main之后");
main();
|
总结
- 优点:代码结构清晰,十分优雅
- 缺点:没有错误捕获机制,只能使用
try/catch
;滥用await
会导致性能问题(阻塞)