JS 中的异步发展由 callback -> Promise -> Generator -> async/await,具体处理可以分为并行和串行两种,具体的使用可以自由组合。

异步的需要注意(主要就是单一功能原则,流程清晰):

  • 不要嵌套功能。给他们姓名并将他们放在程序的顶层

  • 利用函数提升来利用你的优势来移动函数

  • 处理每个回调中的每一个错误。使用标准来帮助你

  • 创建可重用的函数并将它们放在模块中以减少理解代码所需的认知负载。将代码分割成小块这样也可以帮助您处理错误,编写测试,强制您为您的代码创建稳定且文档化的公共API,并有助于重构

回调函数( Callback )

回调函数是将将代码的执行权限给予调用方,当调用方执行完成条件后调用此函数将权限交回到被调用方。

缺点在与容易形成回调地狱(回调层层嵌套,不能清晰的查看代码运行逻辑)。

  1. // 定时器是典型的回调
  2. function doSomething(data){
  3. console.log("回调完成",data)
  4. }
  5. setTimeout(()=>{
  6. doSomething("test callback");
  7. });
  8. function errHandle(err,data){
  9. if(err) return console.log(err);
  10. console.log(data);
  11. }
  12. function read(params,callback){
  13. let err,data;
  14. //...处理参数拿到数据
  15. //回调数据
  16. callback(err,data);
  17. }
  18. read({key:"value"},errHandle);
  19. // 回调地狱,需要在前一次回调完成的时进行下一次调用
  20. read({},(err,data) =>{
  21. if(err)return console.log(err);
  22. read({data:data},(err,data1) => {
  23. if(err)return console.log(err);
  24. read({data1:data1},(err,data2) => {
  25. if(err)return console.log(err);
  26. //...可以嵌套多层,越多越难调试
  27. });
  28. });
  29. });
  30. // 需要自己注意代码整洁和模块化,流程化,不然还是会出现回调地狱
  31. const getData1 = () => Promise.resolve("data1");
  32. const getData2 = (data1) => Promise.resolve(data1+",data2");
  33. const getData3 = (data2) => Promise.resolve(data2+",data3");
  34. getData1().then((resData1) => {
  35. getData2(resData1).then((resData2) => {
  36. getData3(resData2).then((resData3)=>{
  37. console.log('resData3:', resData3)
  38. })
  39. });
  40. });

Promise

因为 Promise.prototype.thenPromise.prototype.catch 方法返回的是 promise, 所以它们可以被链式调用。

  1. 并行

    • 一个成功,返回成功结果,全部失败,返回失败
    • 一个失败,返回失败结果,全部成功,返回成功
    • 等待全部完成,返回成功或失败结果数组
  2. 串行

    • 一个失败就失败
    • 失败后处理错误,继续执行
  1. //# 1. 简单调用
  2. // 成功的回调函数
  3. function successCallback(result) {
  4. console.log('成功: ' + result);
  5. }
  6. // 失败的回调函数
  7. function failureCallback(error) {
  8. console.log('失败: ' + error);
  9. }
  10. function createPromsie() {
  11. return new Promise((resolve, reject) => {
  12. setTimeout(function () {
  13. resolve('aaaaaa');
  14. }, 2000);
  15. });
  16. }
  17. createPromsie().then(successCallback, failureCallback);
  18. //# 2. 链式调用,最后捕捉错误,适合遇到错误就停止链式执行的情况
  19. //链式调用如果没有返回值,则返回值是 undefined
  20. function createPromsie(str) {
  21. return new Promise((resolve, reject) => {
  22. setTimeout(function () {
  23. resolve(str);
  24. }, 2000);
  25. });
  26. }
  27. createPromsie('11111')
  28. .then(res => {
  29. console.log('1. ', res);
  30. return createPromsie(res + ',22222');
  31. })
  32. .then(res => {
  33. console.log('2. ', res);
  34. return createPromsie(res + ',33333');
  35. })
  36. .then(res => {
  37. console.log('3. ', res);
  38. return Promise.reject('错误');
  39. })
  40. .catch(err => {
  41. console.log(err);
  42. });
  43. // 1. 11111
  44. // 2. 11111,22222
  45. // 3. 11111,22222,33333
  46. // 错误
  47. function createPromsie(str) {
  48. return new Promise((resolve, reject) => {
  49. setTimeout(function () {
  50. reject(str);
  51. }, 2000);
  52. });
  53. }
  54. createPromsie('11111')
  55. .then(res => {
  56. console.log('1. ', res);
  57. return createPromsie(res + ',22222');
  58. })
  59. .then(res => {
  60. console.log('2. ', res);
  61. return createPromsie(res + ',33333');
  62. })
  63. .then(res => {
  64. console.log('3. ', res);
  65. return Promise.reject('错误');
  66. })
  67. .catch(err => {
  68. console.log(err);
  69. });
  70. //11111
  71. //# 3. 这种适合单独处理错误,并且不会因为错误而中断执行的时候使用
  72. function createPromsie(str) {
  73. return new Promise((resolve, reject) => {
  74. setTimeout(function () {
  75. reject(str);
  76. }, 2000);
  77. });
  78. }
  79. createPromsie('11111')
  80. .then(
  81. res => {
  82. console.log('1. ', res);
  83. return createPromsie(res + ',22222');
  84. },
  85. err => {
  86. console.log('err1', err);
  87. return err + ',44444';
  88. }
  89. )
  90. .then(
  91. res => {
  92. console.log('2. ', res);
  93. return res + ',33333';
  94. },
  95. err => {
  96. console.log('err2', err);
  97. return err + ',55555';
  98. }
  99. );
  100. // err1 11111
  101. // 2. 11111,44444
  102. //# 4. 和上边的形式相同
  103. function createPromsie(str) {
  104. return new Promise((resolve, reject) => {
  105. setTimeout(function () {
  106. reject(str);
  107. }, 2000);
  108. });
  109. }
  110. createPromsie('11111')
  111. .then(res => {
  112. console.log('1. ', res);
  113. return createPromsie(res + ',22222');
  114. })
  115. .catch(err => {
  116. console.log('err1', err);
  117. return err + ',44444';
  118. })
  119. .then(res => {
  120. console.log('2. ', res);
  121. return res + ',33333';
  122. })
  123. .catch(err => {
  124. console.log('err2', err);
  125. return err + ',55555';
  126. });
  127. // err1 11111
  128. // 2. 11111,44444
  129. //# 5. 最终直接一次
  130. function createPromsie(str) {
  131. return new Promise((resolve, reject) => {
  132. setTimeout(function () {
  133. reject(str);
  134. }, 2000);
  135. });
  136. }
  137. createPromsie('11111')
  138. .then(res => {
  139. console.log('1. ', res);
  140. return createPromsie(res + ',22222');
  141. })
  142. .catch(err => {
  143. console.log('err1', err);
  144. return err + ',44444';
  145. })
  146. .then(res => {
  147. console.log('2. ', res);
  148. return res + ',33333';
  149. })
  150. .catch(err => {
  151. console.log('err2', err);
  152. return err + ',55555';
  153. })
  154. .finally(res=>console.log(res))

全局事件

  1. /* 当 Promise 被拒绝时,会有下文所述的两个事件之一被派发到全局作用域(通常而言,就是window;如果是在 web worker 中使用的话,就是 Worker 或者其他 worker-based 接口)。这两个事件如下所示:
  2. rejectionhandled
  3. 当 Promise 被拒绝、并且在 reject 函数处理该 rejection 之后会派发此事件。
  4. unhandledrejection
  5. 当 Promise 被拒绝,但没有提供 reject 函数来处理该 rejection 时,会派发此事件。
  6. 以上两种情况中,PromiseRejectionEvent 事件都有两个属性,一个是 promise 属性,该属性指向被驳回的 Promise,另一个是 reason 属性,该属性用来说明 Promise 被驳回的原因。
  7. 因此,我们可以通过以上事件为 Promise 失败时提供补偿处理,也有利于调试 Promise 相关的问题。在每一个上下文中,该处理都是全局的,因此不管源码如何,所有的错误都会在同一个处理函数中被捕捉并处理。*/
  8. window.addEventListener("unhandledrejection", event => {
  9. /* 你可以在这里添加一些代码,以便检查
  10. event.promise 中的 promise 和
  11. event.reason 中的 rejection 原因 */
  12. event.preventDefault();
  13. }, false);

旧 API 得封装

  1. const wait = ms => new Promise(resolve => setTimeout(resolve, ms));
  2. wait(10000).then(() => saySomething("10 seconds")).catch(failureCallback);
  3. // setTimeout 并不会reject 因此可以去掉catch
  4. wait(10000).then(() => saySomething("10 seconds"));

Promise.all

Primise.all 是并行运行的,当参数数组中所有的Promise都成功才返回成功,否则返回失败

  1. function createPromsie(str,time) {
  2. return new Promise((resolve, reject) => {
  3. setTimeout(function () {
  4. resolve(str);
  5. }, time);
  6. });
  7. }
  8. function createPromsie1(str,time) {
  9. return new Promise((resolve, reject) => {
  10. setTimeout(function () {
  11. reject(str);
  12. }, time);
  13. });
  14. }
  15. // 简单使用
  16. Promise.all([createPromsie('1',1000),createPromsie('2',2000),createPromsie('3',3000)])
  17. .then(([result1, result2, result3]) => { /* use result1, result2 and result3 */ });
  18. //链式
  19. // 组合
  20. const func1 = () => createPromsie('1',1000);
  21. const func2 = () => createPromsie('2',2000);
  22. const func3 = () => createPromsie('3',3000);
  23. [func1, func2, func3].reduce((p, f) => p.then(f), Promise.resolve())
  24. .then(result3 => { /* use result3 */ });
  25. Promise.resolve().then(func1).then(func2).then(func3);

Promise.race

Primise.race 也是并行运行的

  1. function createPromsie(str,time) {
  2. return new Promise((resolve, reject) => {
  3. setTimeout(function () {
  4. reject(str);
  5. }, time);
  6. });
  7. }
  8. Promise.race([createPromsie('1',1000),createPromsie('2',2000),createPromsie('3',3000)]);

执行时机

  1. // then 中的函数被放置在一个微任务队列中异步调用,只有当事件队列全部执行结束后才会开始执行
  2. // 具体要参考 nodejs 或者 浏览器的事件循环
  3. Promise.resolve().then(() => console.log(2));
  4. console.log(1); // 1, 2
  5. const wait = ms => new Promise(resolve => setTimeout(resolve, ms));
  6. wait().then(() => console.log(4));
  7. Promise.resolve().then(() => console.log(2)).then(() => console.log(3));
  8. console.log(1); // 1, 2, 3, 4

Promise嵌套

  1. /*嵌套 Promise 是一种可以限制 catch 语句的作用域的控制结构写法。明确来说,嵌套的 catch 仅捕捉在其之前同时还必须是其作用域的 failureres,而捕捉不到在其链式以外或者其嵌套域以外的 error。如果使用正确,那么可以实现高精度的错误修复。*/
  2. const createResolvePromise = (str, time) =>
  3. new Promise((res, rej) => setTimeout(() => res(str), time));
  4. const createRejectPromise = (str, time) =>
  5. new Promise((res, rej) => setTimeout(() => rej(str), time));
  6. let doSomethingCritical = () => createResolvePromise('1', 1000);
  7. let doSomethingOptional = () => createResolvePromise('2', 1000);
  8. let doSomethingExtraNice = params => createResolvePromise('3', 1000);
  9. let moreCriticalStuff = () => createResolvePromise('4', 1000);
  10. doSomethingCritical()
  11. .then(result =>
  12. doSomethingOptional()
  13. .then(optionalResult => {
  14. console.log(optionalResult);
  15. return doSomethingExtraNice(optionalResult);
  16. })
  17. .catch(e => {
  18. console.log(e);
  19. })
  20. ) // 即使有异常也会忽略,继续运行;(最后会输出)
  21. .then(() => moreCriticalStuff())
  22. .catch(e => console.log('Critical failure: ' + e));
  23. // 2
  24. doSomethingCritical = () => createRejectPromise('1', 1000);
  25. doSomethingCritical()
  26. .then(result =>
  27. doSomethingOptional()
  28. .then(optionalResult => {
  29. console.log(optionalResult);
  30. return doSomethingExtraNice(optionalResult);
  31. })
  32. .catch(e => {
  33. console.log(e);
  34. })
  35. ) // 即使有异常也会忽略,继续运行;(最后会输出)
  36. .then(() => moreCriticalStuff())
  37. .catch(e => console.log('Critical failure: ' + e));
  38. //1
  39. doSomethingOptional = () => createRejectPromise('1', 1000);
  40. doSomethingCritical()
  41. .then(result =>
  42. doSomethingOptional()
  43. .then(optionalResult => {
  44. console.log(optionalResult);
  45. return doSomethingExtraNice(optionalResult);
  46. })
  47. .catch(e => {
  48. console.log(e);
  49. })
  50. ) // 即使有异常也会忽略,继续运行;(最后会输出)
  51. .then((res) =>{console.log(res,"aaa"); return moreCriticalStuff()})
  52. .catch(e => console.log('Critical failure: ' + e));
  53. // 1
  54. // undefined "aaa"

async/await

对于 Promise 的语法糖,同步写法。

  1. /* 学习来源 https://careersjs.com/magazine/async-patterns/ */
  2. function doSomethingAsync(val) {
  3. return new Promise.resolve(val || 3)
  4. }
  5. function doAnotherAsync(val) {
  6. return new Promise.resolve(val || 4)
  7. }
  8. /* 比较混乱的 */
  9. function getVals1() {
  10. return doSomethingAsync().then(function (val) {
  11. return doAnotherAsync(val).then(function (anotherVal) {
  12. // Here we need both val and anotherVal so we nested
  13. return val + anotherVal
  14. })
  15. })
  16. }
  17. /* 稍好一点的 */
  18. function getVals2() {
  19. let value
  20. return doSomethingAsync().then(function (val) {
  21. // send val to the outer scope so others can use it
  22. value = val
  23. return doAnotherAsync(val)
  24. }).then(function (anotherVal) {
  25. // Here we grab value from outside
  26. return value + anotherVal
  27. })
  28. }
  29. /* 箭头函数的 ,减少了代码,并没有什么实质的改变*/
  30. function getVals3() {
  31. return doSomethingAsync().then(val => doAnotherAsync(val).then(anotherVal => val + anotherVal))
  32. }
  33. function getVals4() {
  34. let value
  35. return doSomethingAsync()
  36. .then(val => (value = val, doAnotherAsync(val)))
  37. .then(anotherVal => value + anotherVal)
  38. }
  39. /* 结构清晰的 */
  40. async function getVals5() {
  41. let val = await doSomethingAsync()
  42. let anotherVal = await doAnotherAsync(val)
  43. return val + anotherVal
  44. }
  45. /* “随机”顺序异步操作,这两者的代码并没有什么区别,但是async比promise.then的可读性摇号一点 */
  46. function getVals6() {
  47. return doSomethingAsync()
  48. .then(val => doAnotherAsync(val))
  49. .then(anotherVal => /* We don't need 'val' here */ 2 * anotherVal)
  50. }
  51. async function getVals7() {
  52. let val = await doSomethingAsync()
  53. let anotherVal = await doAnotherAsync(val)
  54. return 2 * anotherVal
  55. }
  56. /* “随机”并行异步操作 */
  57. function getVals8() {
  58. return Promise.all([doSomethingAsync(), doAnotherAsync()])
  59. .then(function ([val, anotherVal]) {
  60. return val + anotherVal
  61. })
  62. }
  63. async function getVals9() {
  64. let [val, anotherVal] = await Promise.all([doSomethingAsync(), doAnotherAsync()])
  65. return val + anotherVal
  66. }
  67. /* 迭代并行异步操作 ,简单的操作async并不能提供太多的清晰度,但是一旦比较复杂就会提升较多的清晰度*/
  68. var values = [1, 2, 3, 4, 5, 6];
  69. function doAsyncToAll1(values /* array */) {
  70. return Promise.all(values.map(doSomethingAsync))
  71. }
  72. async function doAsyncToAll2(values /* array */) {
  73. return await Promise.all(values.map(doSomethingAsync))
  74. }
  75. function doAsyncToAll3(values /* array */) {
  76. return Promise.all(values.map(val => {
  77. return doSomethingAsync(val)
  78. .then(anotherVal => doAnotherAsync(anotherValue * 2))
  79. }))
  80. }
  81. function doAsyncToAll4(values /* array */) {
  82. return Promise.all(values.map(async val => {
  83. let anotherVal = await doSomethingAsync(val)
  84. return doAnotherAsync(anotherValue * 2)
  85. }))
  86. }
  87. /* 迭代顺序异步操作 */
  88. function doAsyncToAllSequentially1(values) {
  89. return values.reduce((previousOperation, val) => {
  90. return previousOperation.then(() => doSomethingAsync(val))
  91. }, Promise.resolve())
  92. }
  93. async function doAsyncToAllSequentially2(values) {
  94. for (let val of values) {
  95. await doSomethingAsync(val)
  96. }
  97. }
  98. /* 当不仅仅是使操作顺序进行,则可以继续使用 reduce */
  99. function doAsyncToAllSequentially3(values) {
  100. return values.reduce((previousOperation, val) => {
  101. return previousOperation.then(
  102. total => doSomethingAsync(val).then(
  103. newVal => total + newVal
  104. )
  105. )
  106. }, Promise.resolve(0))
  107. }
  108. /* 这样会更加清晰*/
  109. async function doAsyncToAllSequentially4(values) {
  110. let total = 0
  111. for (let val of values) {
  112. let newVal = await doSomethingAsync(val)
  113. total += newVal
  114. }
  115. return total
  116. }
  117. /* 也可以使用 reduce */
  118. async function doAsyncToAllSequentially5(values) {
  119. return values.reduce(async (previous, val) => {
  120. let total = await previous
  121. let newVal = await doSomethingAsync(val)
  122. return total + newVal
  123. }, Promise.resolve(0))
  124. }

错误处理

适用于同步情况,不用每个Promise去写catch,只需要在使用的时候来处理一次

  1. (async () => {
  2. const fetchData = () => {
  3. return new Promise((resolve, reject) => {
  4. setTimeout(() => {
  5. resolve('fetch data is me')
  6. }, 1000)
  7. })
  8. }
  9. // 抽离成公共方法
  10. const awaitWrap = (promise) => {
  11. return promise
  12. .then(data => [null, data])
  13. .catch(err => [err, null])
  14. }
  15. const [err, data] = await awaitWrap(fetchData())
  16. console.log('err', err)
  17. console.log('data', data)
  18. // err null
  19. // data fetch data is me
  20. })()
  21. // 作者:Vincent.W
  22. // 链接:https://juejin.im/post/6844903767129718791
  1. function awaitWrap<T, U = any>(promise: Promise<T>): Promise<[U | null, T | null]> {
  2. return promise
  3. .then<[null, T]>((data: T) => [null, data])
  4. .catch<[U, null]>(err => [err, null])
  5. }
  6. // 作者:Vincent.W
  7. // 链接:https://juejin.im/post/6844903767129718791

Generator

generator 中文名叫构造器,返回一个生成器对象。

  1. function* foo() {
  2. for (let i = 1; i <= 3; i++) {
  3. let x = yield `等我一下呗,i = ${i}`;
  4. console.log(x);
  5. }
  6. }
  7. setTimeout(() => {
  8. console.log('终于轮到我了');
  9. }, 1);
  10. var a = foo();
  11. console.log(a); // foo {<closed>}
  12. var b = a.next();
  13. console.log(b); // {value: "等我一下呗,i = 1", done: false}
  14. var c = a.next();
  15. console.log(c); // {value: "等我一下呗,i = 2", done: false}
  16. var d = a.next();
  17. console.log(d); // {value: "等我一下呗,i = 3", done: false}
  18. var e = a.next();
  19. console.log(e); // {value: undefined, done: true}
  20. // 终于轮到我了
  21. //Generator.prototype.throw()
  22. function* gen() {
  23. while(true) {
  24. try {
  25. yield 42;
  26. } catch(e) {
  27. console.log("Error caught!");
  28. }
  29. }
  30. }
  31. var g = gen();
  32. g.next(); // { value: 42, done: false }
  33. g.throw(new Error("Something went wrong")); // "Error caught!"
  34. //Generator.prototype.return() 返回给定的值并结束生成器。
  35. function* gen() {
  36. yield 1;
  37. yield 2;
  38. yield 3;
  39. }
  40. var g = gen();
  41. g.next(); // { value: 1, done: false }
  42. g.return("foo"); // { value: "foo", done: true }
  43. g.next(); // { value: undefined, done: true }