金沙棋牌官方平台

当前位置:金沙棋牌 > 金沙棋牌官方平台 > 函数简化异步代码,中的神器

函数简化异步代码,中的神器

来源:http://www.logblo.com 作者:金沙棋牌 时间:2019-11-22 11:09

用 Async 函数简化异步代码

2017/04/08 · JavaScript · 异步

原文出处: Joe Zimmerman , Nilson Jacques   译文出处:oschina   

Promise 在 JavaScript 上发布之初就在互联网上流行了起来 — 它们帮开发人员摆脱了回调地狱,解决了在很多地方困扰 JavaScript 开发者的异步问题。但 Promises 也远非完美。它们一直请求回调,在一些复杂的问题上仍会有些杂乱和一些难以置信的冗余。

随着 ES6 的到来(现在被称作 ES2015),除了引入 Promise 的规范,不需要请求那些数不尽的库之外,我们还有了生成器。生成器可在函数内部停止执行,这意味着可把它们封装在一个多用途的函数中,我们可在代码移动到下一行之前等待异步操作完成。突然你的异步代码可能就开始看起来同步了。

这只是第一步。异步函数因今年加入 ES2017,已进行标准化,本地支持也进一步优化。异步函数的理念是使用生成器进行异步编程,并给出他们自己的语义和语法。因此,你无须使用库来获取封装的实用函数,因为这些都会在后台处理。

运行文章中的 async/await 实例,你需要一个能兼容的浏览器。

姓名:岳沁

Promise in js

本文作者: 伯乐在线 - ascoders 。未经作者许可,禁止转载!
欢迎加入伯乐在线 专栏作者。

运行兼容

在客户端,Chrome、Firefox 和 Opera 能很好地支持异步函数。

金沙棋牌官方平台 1

(点击图片进行页面跳转)

从 7.6 版本开始,Node.js 默认启用 async/await。

学号:17101223458

回调函数真正的问题在于他剥夺了我们使用 return 和 throw 这些关键字的能力。而 Promise 很好地解决了这一切。

根据笔者的项目经验,本文讲解了从函数回调,到 es7 规范的异常处理方式。异常处理的优雅性随着规范的进步越来越高,不要害怕使用 try catch,不能回避异常处理。

异步函数和生成器对比

这有个使用生成器进行异步编程的实例,用的是 Q 库:

var doAsyncOp = Q.async(function* () {   var val = yield asynchronousOperation();   console.log(val);   return val; });

1
2
3
4
5
var doAsyncOp = Q.async(function* () {
  var val = yield asynchronousOperation();
  console.log(val);
  return val;
});

Q.async 是个封装函数,处理场景后的事情。其中 * 表示作为一个生成器函数的功能,yield 表示停止函数,并用封装函数代替。Q.async 将会返回一个函数,你可对它赋值,就像赋值 doAsyncOp 一样,随后再调用。

ES7 中的新语法更简洁,操作示例如下:

async function doAsyncOp () {   var val = await asynchronousOperation();        console.log(val);   return val; };

1
2
3
4
5
async function doAsyncOp () {
  var val = await asynchronousOperation();     
  console.log(val);
  return val;
};

差异不大,我们删除了一个封装的函数和 * 符号,转而用 async 关键字代替。yield 关键字也被 await 取代。这两个例子事实上做的事是相同的:在 asynchronousOperation 完成之后,赋值给 val,然后进行输出并返回结果。

转载自:

2015 年 6 月,ECMAScript 6 的正式版终于发布了。

我们需要一个健全的架构捕获所有同步、异步的异常。业务方不处理异常时,中断函数执行并启用默认处理,业务方也可以随时捕获异常自己处理。

将 Promises 转换成异步函数

如果我们使用 Vanilla Promises 的话前面的示例将会是什么样?

function doAsyncOp () {   return asynchronousOperation().then(function(val) {     console.log(val);     return val;   }); };

1
2
3
4
5
6
function doAsyncOp () {
  return asynchronousOperation().then(function(val) {
    console.log(val);
    return val;
  });
};

这里有相同的代码行数,但这是因为 then 和给它传递的回调函数增加了很多的额外代码。另一个让人厌烦的是两个 return 关键字。这一直有些事困扰着我,因为它很难弄清楚使用 promises 的函数确切的返回是什么。

就像你看到的,这个函数返回一个 promises,将会赋值给 val,猜一下生成器和异步函数示例做了什么!无论你在这个函数返回了什么,你其实是暗地里返回一个 promise 解析到那个值。如果你根本就没有返回任何值,你暗地里返回的 promise 解析为 undefined。

【嵌牛导读】:ES6 原生提供了 Promise 对象。

ECMAScript 是 JavaScript 语言的国际标准,JavaScript 是 ECMAScript 的实现。ES6 的目标,是使得 JavaScript 语言可以用来编写大型的复杂的应用程序,成为企业级开发语言。

优雅的异常处理方式就像冒泡事件,任何元素可以自由拦截,也可以放任不管交给顶层处理。

链式操作

Promise 之所以能受到众人追捧,其中一个方面是因为它能以链式调用的方式把多个异步操作连接起来,避免了嵌入形式的回调。不过 async 函数在这个方面甚至比 Promise 做得还好。

下面演示了如何使用 Promise 来进行链式操作(我们只是简单的多次运行 asynchronousOperation 来进行演示)。

function doAsyncOp() {   return asynchronousOperation()     .then(function(val) {       return asynchronousOperation(val);     })     .then(function(val) {       return asynchronousOperation(val);     })     .then(function(val) {       return asynchronousOperation(val);     }); }

1
2
3
4
5
6
7
8
9
10
11
12
function doAsyncOp() {
  return asynchronousOperation()
    .then(function(val) {
      return asynchronousOperation(val);
    })
    .then(function(val) {
      return asynchronousOperation(val);
    })
    .then(function(val) {
      return asynchronousOperation(val);
    });
}

使用 async 函数,只需要像编写同步代码那样调用 asynchronousOperation:

async function doAsyncOp () {   var val = await asynchronousOperation();   val = await asynchronousOperation(val);   val = await asynchronousOperation(val);   return await asynchronousOperation(val); };

1
2
3
4
5
6
async function doAsyncOp () {
  var val = await asynchronousOperation();
  val = await asynchronousOperation(val);
  val = await asynchronousOperation(val);
  return await asynchronousOperation(val);
};

甚至最后的 return 语句中都不需要使用 await,因为用或不用,它都返回了包含了可处理终值的 Promise。

所谓 Promise,就是一个对象,用来传递异步操作的消息。它代表了某个未来才会知道结果的事件(通常是一个异步操作),并且这个事件提供统一的 API,可供进一步处理。

概念

文字讲解仅是背景知识介绍,不包含对代码块的完整解读,不要忽略代码块的阅读。

并发操作

Promise 还有另一个伟大的特性,它们可以同时进行多个异步操作,等他们全部完成之后再继续进行其它事件。ES2015 规范中提供了 Promise.all(),就是用来干这个事情的。

这里有一个示例:

function doAsyncOp() {   return Promise.all([     asynchronousOperation(),     asynchronousOperation()   ]).then(function(vals) {     vals.forEach(console.log);     return vals;   }); }

1
2
3
4
5
6
7
8
9
function doAsyncOp() {
  return Promise.all([
    asynchronousOperation(),
    asynchronousOperation()
  ]).then(function(vals) {
    vals.forEach(console.log);
    return vals;
  });
}

Promise.all() 也可以当作 async 函数使用:

async function doAsyncOp() {   var vals = await Promise.all([     asynchronousOperation(),     asynchronousOperation()   ]);   vals.forEach(console.log.bind(console));   return vals; }

1
2
3
4
5
6
7
8
async function doAsyncOp() {
  var vals = await Promise.all([
    asynchronousOperation(),
    asynchronousOperation()
  ]);
  vals.forEach(console.log.bind(console));
  return vals;
}

这里就算使用了 Promise.all,代码仍然很清楚。

【嵌牛鼻子】:Promise

ES6 原生提供了 Promise 对象。

1. 回调

如果在回调函数中直接处理了异常,是最不明智的选择,因为业务方完全失去了对异常的控制能力。

下方的函数 请求处理 不但永远不会执行,还无法在异常时做额外的处理,也无法阻止异常产生时笨拙的 console.log('请求失败') 行为。

function fetch(callback) { setTimeout(() = > { console.log('请求失败') }) } fetch(() = > { console.log('请求处理') // 永远不会执行 })

1
2
3
4
5
6
7
8
9
function fetch(callback) {
    setTimeout(() = > {
        console.log('请求失败')
    })
}
fetch(() = > {
    console.log('请求处理') // 永远不会执行
})

处理拒绝

Promises 可以被接受(resovled)也可以被拒绝(rejected)。被拒绝的 Promise 可以通过一个函数来处理,这个处理函数要传递给 then,作为其第二个参数,或者传递给 catch 方法。现在我们没有使用 Promise API 中的方法,应该怎么处理拒绝?可以通过 try 和 catch 来处理。使用 async 函数的时候,拒绝被当作错误来传递,这样它们就可以通过 JavaScript 本身支持的错误处理代码来处理。

function doAsyncOp() {   return asynchronousOperation()     .then(function(val) {       return asynchronousOperation(val);     })     .then(function(val) {       return asynchronousOperation(val);     })     .catch(function(err) {       console.error(err);     }); }

1
2
3
4
5
6
7
8
9
10
11
12
function doAsyncOp() {
  return asynchronousOperation()
    .then(function(val) {
      return asynchronousOperation(val);
    })
    .then(function(val) {
      return asynchronousOperation(val);
    })
    .catch(function(err) {
      console.error(err);
    });
}

这与我们链式处理的示例非常相似,只是把它的最后一环改成了调用 catch。如果用 async 函数来写,会像下面这样。

async function doAsyncOp () {   try {     var val = await asynchronousOperation();     val = await asynchronousOperation(val);     return await asynchronousOperation(val);   } catch (err) {     console.err(err);   } };

1
2
3
4
5
6
7
8
9
async function doAsyncOp () {
  try {
    var val = await asynchronousOperation();
    val = await asynchronousOperation(val);
    return await asynchronousOperation(val);
  } catch (err) {
    console.err(err);
  }
};

它不像其它往 async 函数的转换那样简洁,但是确实跟写同步代码一样。如果你在这里不捕捉错误,它会延着调用链一直向上抛出,直到在某处被捕捉处理。如果它一直未被捕捉,它最终会中止程序并抛出一个运行时错误。Promise 以同样的方式运作,只是拒绝不当作错误来处理;它们可能只是一个说明错误情况的字符串。如果你不捕捉被创建为错误的拒绝,你会看到一个运行时错误,不过如果你只是使用一个字符串,会失败却不会有输出。

【嵌牛提问】:如何提高Promise效率?

所谓 Promise,就是一个对象,用来传递异步操作的消息。它代表了某个未来才会知道结果的事件(通常是一个异步操作),并且这个事件提供统一的 API,可供进一步处理。

2. 回调,无法捕获的异常

回调函数有同步和异步之分,区别在于对方执行回调函数的时机,异常一般出现在请求、数据库连接等操作中,这些操作大多是异步的。

异步回调中,回调函数的执行栈与原函数分离开,导致外部无法抓住异常。

从下文开始,我们约定用 setTimeout 模拟异步操作

function fetch(callback) { setTimeout(() = > { throw Error('请求失败') }) } try { fetch(() = > { console.log('请求处理') // 永远不会执行 }) } catch (error) { console.log('触发异常', error) // 永远不会执行 } // 程序崩溃 // Uncaught Error: 请求失败

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function fetch(callback) {
    setTimeout(() = > {
        throw Error('请求失败')
    })
}
try {
    fetch(() = > {
        console.log('请求处理') // 永远不会执行
    })
} catch (error) {
    console.log('触发异常', error) // 永远不会执行
}
// 程序崩溃
// Uncaught Error: 请求失败

中断 Promise

拒绝原生的 Promise,只需要使用 Promise 构建函数中的 reject 就好,当然也可以直接抛出错误——在 Promise 的构造函数中,在 then 或 catch 的回调中抛出都可以。如果是在其它地方抛出错误,Promise 就管不了了。

这里有一些拒绝 Promise 的示例:

function doAsyncOp() {   return new Promise(function(resolve, reject) {     if (somethingIsBad) {       reject("something is bad");     }     resolve("nothing is bad");   }); } /*-- or --*/ function doAsyncOp() {   return new Promise(function(resolve, reject) {     if (somethingIsBad) {       reject(new Error("something is bad"));     }     resolve("nothing is bad");   }); } /*-- or --*/ function doAsyncOp() {   return new Promise(function(resolve, reject) {     if (somethingIsBad) {       throw new Error("something is bad");     }     resolve("nothing is bad");   }); }

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 doAsyncOp() {
  return new Promise(function(resolve, reject) {
    if (somethingIsBad) {
      reject("something is bad");
    }
    resolve("nothing is bad");
  });
}
 
/*-- or --*/
 
function doAsyncOp() {
  return new Promise(function(resolve, reject) {
    if (somethingIsBad) {
      reject(new Error("something is bad"));
    }
    resolve("nothing is bad");
  });
}
 
/*-- or --*/
 
function doAsyncOp() {
  return new Promise(function(resolve, reject) {
    if (somethingIsBad) {
      throw new Error("something is bad");
    }
    resolve("nothing is bad");
  });
}

一般来说,最好使用 new Error,因为它会包含错误相关的其它信息,比如抛出位置的行号,以及可能会有用的调用栈。

这里有一些抛出 Promise 不能捕捉的错误的示例:

function doAsyncOp() {   // the next line will kill execution   throw new Error("something is bad");   return new Promise(function(resolve, reject) {     if (somethingIsBad) {       throw new Error("something is bad");     }     resolve("nothing is bad");   }); } // assume `doAsyncOp` does not have the killing error function x() {   var val = doAsyncOp().then(function() {     // this one will work just fine     throw new Error("I just think an error should be here");   });   // this one will kill execution   throw new Error("The more errors, the merrier");   return val; }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function doAsyncOp() {
  // the next line will kill execution
  throw new Error("something is bad");
  return new Promise(function(resolve, reject) {
    if (somethingIsBad) {
      throw new Error("something is bad");
    }
    resolve("nothing is bad");
  });
}
 
// assume `doAsyncOp` does not have the killing error
function x() {
  var val = doAsyncOp().then(function() {
    // this one will work just fine
    throw new Error("I just think an error should be here");
  });
  // this one will kill execution
  throw new Error("The more errors, the merrier");
  return val;
}

在 async 函数的 Promise 中抛出错误就不会产生有关范围的问题——你可以在 async 函数中随时随地抛出错误,它总会被 Promise 抓住:

async function doAsyncOp() {   // the next line is fine   throw new Error("something is bad");   if (somethingIsBad) {     // this one is good too     throw new Error("something is bad");   }   return "nothing is bad"; }  // assume `doAsyncOp` does not have the killing error async function x() {   var val = await doAsyncOp();   // this one will work just fine   throw new Error("I just think an error should be here");   return val; }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
async function doAsyncOp() {
  // the next line is fine
  throw new Error("something is bad");
  if (somethingIsBad) {
    // this one is good too
    throw new Error("something is bad");
  }
  return "nothing is bad";
 
// assume `doAsyncOp` does not have the killing error
async function x() {
  var val = await doAsyncOp();
  // this one will work just fine
  throw new Error("I just think an error should be here");
  return val;
}

当然,我们永远不会运行到 doAsyncOp 中的第二个错误,也不会运行到 return 语句,因为在那之前抛出的错误已经中止了函数运行。

【嵌牛正文】:

Promise 对象有以下两个特点。

3. 回调,不可控的异常

我们变得谨慎,不敢再随意抛出异常,这已经违背了异常处理的基本原则。

虽然使用了 error-first 约定,使异常看起来变得可处理,但业务方依然没有对异常的控制权,是否调用错误处理取决于回调函数是否执行,我们无法知道调用的函数是否可靠。

更糟糕的问题是,业务方必须处理异常,否则程序挂掉就会什么都不做,这对大部分不用特殊处理异常的场景造成了很大的精神负担。

function fetch(handleError, callback) { setTimeout(() = > { handleError('请求失败') }) } fetch(() = > { console.log('失败处理') // 失败处理 }, error = > { console.log('请求处理') // 永远不会执行 })

1
2
3
4
5
6
7
8
9
10
11
function fetch(handleError, callback) {
    setTimeout(() = > {
        handleError('请求失败')
    })
}
fetch(() = > {
    console.log('失败处理') // 失败处理
}, error = > {
    console.log('请求处理') // 永远不会执行
})

问题

如果你刚开始使用 async 函数,需要小心嵌套函数的问题。比如,如果你的 async 函数中有另一个函数(通常是回调),你可能认为可以在其中使用 await ,但实际不能。你只能直接在 async 函数中使用 await 。

比如,这段代码无法运行:

async function getAllFiles(fileNames) {   return Promise.all(     fileNames.map(function(fileName) {       var file = await getFileAsync(fileName);       return parse(file);     })   ); }

1
2
3
4
5
6
7
8
async function getAllFiles(fileNames) {
  return Promise.all(
    fileNames.map(function(fileName) {
      var file = await getFileAsync(fileName);
      return parse(file);
    })
  );
}

第 4 行的 await 无效,因为它是在一个普通函数中使用的。不过可以通过为回调函数添加 async 关键字来解决这个问题。

async function getAllFiles(fileNames) {   return Promise.all(     fileNames.map(async function(fileName) {       var file = await getFileAsync(fileName);       return parse(file);     })   ); }

1
2
3
4
5
6
7
8
async function getAllFiles(fileNames) {
  return Promise.all(
    fileNames.map(async function(fileName) {
      var file = await getFileAsync(fileName);
      return parse(file);
    })
  );
}

你看到它的时候会觉得理所当然,即便如此,仍然需要小心这种情况。

也许你还想知道等价的使用 Promise 的代码:

function getAllFiles(fileNames) {   return Promise.all(     fileNames.map(function(fileName) {       return getFileAsync(fileName).then(function(file) {         return parse(file);       });     })   ); }

1
2
3
4
5
6
7
8
9
function getAllFiles(fileNames) {
  return Promise.all(
    fileNames.map(function(fileName) {
      return getFileAsync(fileName).then(function(file) {
        return parse(file);
      });
    })
  );
}

接下来的问题是关于把 async 函数看作同步函数。需要记住的是,async 函数内部的的代码是同步运行的,但是它会立即返回一个 Promise,并继续运行外面的代码,比如:

var a = doAsyncOp(); // one of the working ones from earlier console.log(a); a.then(function() {   console.log("`a` finished"); }); console.log("hello"); /* -- will output -- */ Promise Object hello `a` finished

1
2
3
4
5
6
7
8
9
10
11
var a = doAsyncOp(); // one of the working ones from earlier
console.log(a);
a.then(function() {
  console.log("`a` finished");
});
console.log("hello");
 
/* -- will output -- */
Promise Object
hello
`a` finished

你会看到 async 函数实际使用了内置的 Promise。这让我们思考 async 函数中的同步行为,其它人可以通过普通的 Promise API 调用我们的 async 函数,也可以使用它们自己的 async 函数来调用。

Promise in js

(1)对象的状态不受外界影响。Promise 对象代表一个异步操作,有三种状态:Pending(进行中)、Resolved(已完成,又称 Fulfilled)和 Rejected(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。这也是 Promise 这个名字的由来,它的英语意思就是「承诺」,表示其他手段无法改变。

番外 Promise 基础

Promise 是一个承诺,只可能是成功、失败、无响应三种情况之一,一旦决策,无法修改结果。

Promise金沙棋牌官方平台, 不属于流程控制,但流程控制可以用多个 Promise 组合实现,因此它的职责很单一,就是对一个决议的承诺。

resolve 表明通过的决议,reject 表明拒绝的决议,如果决议通过,then 函数的第一个回调会立即插入 microtask 队列,异步立即执行

简单补充下事件循环的知识,js 事件循环分为 macrotask 和 microtask。 microtask 会被插入到每一个 macrotask 的尾部,所以 microtask 总会优先执行,哪怕 macrotask 因为 js 进程繁忙被 hung 住。 比如 setTimeout setInterval 会插入到 macrotask 中。

const promiseA = new Promise((resolve, reject) = > { resolve('ok') }) promiseA.then(result = > { console.log(result) // ok })

1
2
3
4
5
6
const promiseA = new Promise((resolve, reject) = > {
    resolve('ok')
})
promiseA.then(result = > {
    console.log(result) // ok
})

如果决议结果是决绝,那么 then 函数的第二个回调会立即插入 microtask 队列。

const promiseB = new Promise((resolve, reject) = > { reject('no') }) promiseB.then(result = > { console.log(result) // 永远不会执行 }, error = > { console.log(error) // no })

1
2
3
4
5
6
7
8
const promiseB = new Promise((resolve, reject) = > {
    reject('no')
})
promiseB.then(result = > {
    console.log(result) // 永远不会执行
}, error = > {
    console.log(error) // no
})

如果一直不决议,此 promise 将处于 pending 状态。

const promiseC = new Promise((resolve, reject) = > { // nothing }) promiseC.then(result = > { console.log(result) // 永远不会执行 }, error = > { console.log(error) // 永远不会执行 })

1
2
3
4
5
6
7
8
const promiseC = new Promise((resolve, reject) = > {
    // nothing
})
promiseC.then(result = > {
    console.log(result) // 永远不会执行
}, error = > {
    console.log(error) // 永远不会执行
})

未捕获的 reject 会传到末尾,通过 catch 接住

const promiseD = new Promise((resolve, reject) = > { reject('no') }) promiseD.then(result = > { console.log(result) // 永远不会执行 }). catch (error = > { console.log(error) // no })

1
2
3
4
5
6
7
8
9
const promiseD = new Promise((resolve, reject) = > {
    reject('no')
})
promiseD.then(result = > {
    console.log(result) // 永远不会执行
}).
catch (error = > {
    console.log(error) // no
})

resolve 决议会被自动展开(reject 不会)

const promiseE = new Promise((resolve, reject) = > { return new Promise((resolve, reject) = > { resolve('ok') }) }) promiseE.then(result = > { console.log(result) // ok })

1
2
3
4
5
6
7
8
const promiseE = new Promise((resolve, reject) = > {
    return new Promise((resolve, reject) = > {
        resolve('ok')
    })
})
promiseE.then(result = > {
    console.log(result) // ok
})

链式流,then 会返回一个新的 Promise,其状态取决于 then 的返回值。

const promiseF = new Promise((resolve, reject) = > { resolve('ok') }) promiseF.then(result = > { return Promise.reject('error1') }).then(result = > { console.log(result) // 永远不会执行 return Promise.resolve('ok1') // 永远不会执行 }).then(result = > { console.log(result) // 永远不会执行 }). catch (error = > { console.log(error) // error1 })

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const promiseF = new Promise((resolve, reject) = > {
    resolve('ok')
})
promiseF.then(result = > {
    return Promise.reject('error1')
}).then(result = > {
    console.log(result) // 永远不会执行
    return Promise.resolve('ok1') // 永远不会执行
}).then(result = > {
    console.log(result) // 永远不会执行
}).
catch (error = > {
    console.log(error) // error1
})

如今,更好的异步代码!

即使你本身不能使用异步代码,你也可以进行编写或使用工具将其编译为 ES5。 异步函数能让代码更易于阅读,更易于维护。 只要我们有 source maps,我们可以随时使用更干净的 ES2017 代码。

有许多可以将异步功能(和其他 ES2015+功能)编译成 ES5 代码的工具。 如果您使用的是 Babel,这只是安装 ES2017 preset 的例子。

1 赞 1 收藏 评论

金沙棋牌官方平台 2

回调函数真正的问题在于他剥夺了我们使用 return 和 throw 这些关键字的能力。而 Promise 很好地解决了这一切。

(2)一旦状态改变,就不会再变,任何时候都可以得到这个结果。Promise 对象的状态改变,只有两种可能:从 Pending 变为 Resolved 和从 Pending 变为 Rejected。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果。就算改变已经发生了,你再对 Promise 对象添加回调函数,也会立即得到这个结果。这与事件(Event)完全不同,事件的特点是,如果你错过了它,再去监听,是得不到结果的。

4 Promise 异常处理

不仅是 reject,抛出的异常也会被作为拒绝状态被 Promise 捕获。

function fetch(callback) { return new Promise((resolve, reject) = > { throw Error('用户不存在') }) } fetch().then(result = > { console.log('请求处理', result) // 永远不会执行 }). catch (error = > { console.log('请求处理异常', error) // 请求处理异常 用户不存在 })

1
2
3
4
5
6
7
8
9
10
11
12
function fetch(callback) {
    return new Promise((resolve, reject) = > {
        throw Error('用户不存在')
    })
}
fetch().then(result = > {
    console.log('请求处理', result) // 永远不会执行
}).
catch (error = > {
    console.log('请求处理异常', error) // 请求处理异常 用户不存在
})

2015 年 6 月,ECMAScript 6 的正式版终于发布了。

有了 Promise 对象,就可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数。此外,Promise 对象提供统一的接口,使得控制异步操作更加容易。

5 Promise 无法捕获的异常

但是,永远不要在 macrotask 队列中抛出异常,因为 macrotask 队列脱离了运行上下文环境,异常无法被当前作用域捕获。

function fetch(callback) { return new Promise((resolve, reject) = > { setTimeout(() = > { throw Error('用户不存在') }) }) } fetch().then(result = > { console.log('请求处理', result) // 永远不会执行 }). catch (error = > { console.log('请求处理异常', error) // 永远不会执行 }) // 程序崩溃 // Uncaught Error: 用户不存在

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function fetch(callback) {
    return new Promise((resolve, reject) = > {
        setTimeout(() = > {
            throw Error('用户不存在')
        })
    })
}
fetch().then(result = > {
    console.log('请求处理', result) // 永远不会执行
}).
catch (error = > {
    console.log('请求处理异常', error) // 永远不会执行
})
// 程序崩溃
// Uncaught Error: 用户不存在

不过 microtask 中抛出的异常可以被捕获,说明 microtask 队列并没有离开当前作用域,我们通过以下例子来证明:

Promise.resolve(true).then((resolve, reject) = > { throw Error('microtask 中的异常') }). catch (error = > { console.log('捕获异常', error) // 捕获异常 Error: microtask 中的异常 })

1
2
3
4
5
6
Promise.resolve(true).then((resolve, reject) = > {
    throw Error('microtask 中的异常')
}).
catch (error = > {
    console.log('捕获异常', error) // 捕获异常 Error: microtask 中的异常
})

至此,Promise 的异常处理有了比较清晰的答案,只要注意在 macrotask 级别回调中使用 reject,就没有抓不住的异常。

ECMAScript 是 JavaScript 语言的国际标准,JavaScript 是 ECMAScript 的实现。ES6 的目标,是使得 JavaScript 语言可以用来编写大型的复杂的应用程序,成为企业级开发语言。

Promise 也有一些缺点。首先,无法取消 Promise,一旦新建它就会立即执行,无法中途取消。其次,如果不设置回调函数,Promise 内部抛出的错误,不会反应到外部。第三,当处于 Pending 状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。

6 Promise 异常追问

如果第三方函数在 macrotask 回调中以 throw Error 的方式抛出异常怎么办?

function thirdFunction() { setTimeout(() = > { throw Error('就是任性') }) } Promise.resolve(true).then((resolve, reject) = > { thirdFunction() }). catch (error = > { console.log('捕获异常', error) }) // 程序崩溃 // Uncaught Error: 就是任性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function thirdFunction() {
    setTimeout(() = > {
        throw Error('就是任性')
    })
}
Promise.resolve(true).then((resolve, reject) = > {
    thirdFunction()
}).
catch (error = > {
    console.log('捕获异常', error)
})
// 程序崩溃
// Uncaught Error: 就是任性

值得欣慰的是,由于不在同一个调用栈,虽然这个异常无法被捕获,但也不会影响当前调用栈的执行。

我们必须正视这个问题,唯一的解决办法,是第三方函数不要做这种傻事,一定要在 macrotask 抛出异常的话,请改为 reject 的方式。

function thirdFunction() { return new Promise((resolve, reject) = > { setTimeout(() = > { reject('收敛一些') }) }) } Promise.resolve(true).then((resolve, reject) = > { return thirdFunction() }). catch (error = > { console.log('捕获异常', error) // 捕获异常 收敛一些 })

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function thirdFunction() {
    return new Promise((resolve, reject) = > {
        setTimeout(() = > {
            reject('收敛一些')
        })
    })
}
Promise.resolve(true).then((resolve, reject) = > {
    return thirdFunction()
}).
catch (error = > {
    console.log('捕获异常', error) // 捕获异常 收敛一些
})

请注意,如果 return thirdFunction() 这行缺少了 return 的话,依然无法抓住这个错误,这是因为没有将对方返回的 Promise 传递下去,错误也不会继续传递。

我们发现,这样还不是完美的办法,不但容易忘记 return,而且当同时含有多个第三方函数时,处理方式不太优雅:

function thirdFunction() { return new Promise((resolve, reject) = > { setTimeout(() = > { reject('收敛一些') }) }) } Promise.resolve(true).then((resolve, reject) = > { return thirdFunction().then(() = > { return thirdFunction() }).then(() = > { return thirdFunction() }).then(() = > {}) }). catch (error = > { console.log('捕获异常', error) })

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function thirdFunction() {
    return new Promise((resolve, reject) = > {
        setTimeout(() = > {
            reject('收敛一些')
        })
    })
}
Promise.resolve(true).then((resolve, reject) = > {
    return thirdFunction().then(() = > {
        return thirdFunction()
    }).then(() = > {
        return thirdFunction()
    }).then(() = > {})
}).
catch (error = > {
    console.log('捕获异常', error)
})

是的,我们还有更好的处理方式。

概念

var promise = new Promise(function(resolve, reject) {

番外 Generator 基础

generator 是更为优雅的流程控制方式,可以让函数可中断执行:

function* generatorA() { console.log('a') yield console.log('b') } const genA = generatorA() genA.next() // a genA.next() // b

1
2
3
4
5
6
7
8
function* generatorA() {
console.log('a')
yield
console.log('b')
}
const genA = generatorA()
genA.next() // a
genA.next() // b

yield 关键字后面可以包含表达式,表达式会传给 next().value

next() 可以传递参数,参数作为 yield 的返回值。

这些特性足以孕育出伟大的生成器,我们稍后介绍。下面是这个特性的例子:

function * generatorB(count) { console.log(count) const result = yield 5 console.log(result * count) } const genB = generatorB(2) genB.next() // 2 const genBValue = genB.next(7).value // 14 // genBValue undefined

1
2
3
4
5
6
7
8
9
function * generatorB(count) {
    console.log(count)
    const result = yield 5
    console.log(result * count)
}
const genB = generatorB(2)
genB.next() // 2
const genBValue = genB.next(7).value // 14
// genBValue undefined

第一个 next 是没有参数的,因为在执行 generator 函数时,初始值已经传入,第一个 next 的参数没有任何意义,传入也会被丢弃。

const result = yield 5

1
const result = yield 5

这一句,返回值不是想当然的 5。其的作用是将 5 传递给 genB.next(),其值,由下一个 next genB.next(7) 传给了它,所以语句等于 const result = 7

最后一个 genBValue,是最后一个 next 的返回值,这个值,就是函数的 return,显然为 undefined

我们回到这个语句:

const result = yield 5

1
const result = yield 5

如果返回值是 5,是不是就清晰了许多?是的,这种语法就是 await。所以 Async Awaitgenerator 有着莫大的关联,桥梁就是 生成器,我们稍后介绍 生成器

ES6 原生提供了 Promise 对象。

if (/* 异步操作成功 */){

番外 Async Await

如果认为 Generator 不太好理解,那 Async Await 绝对是救命稻草,我们看看它们的特征:

const timeOut = (time = 0) = > new Promise((resolve, reject) = > { setTimeout(() = > { resolve(time + 200) }, time) }) async function main() { const result1 = await timeOut(200) console.log(result1) // 400 const result2 = await timeOut(result1) console.log(result2) // 600 const result3 = await timeOut(result2) console.log(result3) // 800 } main()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const timeOut = (time = 0) = > new Promise((resolve, reject) = > {
    setTimeout(() = > {
        resolve(time + 200)
    }, time)
})
async
function main() {
    const result1 = await timeOut(200)
    console.log(result1) // 400
    const result2 = await timeOut(result1)
    console.log(result2) // 600
    const result3 = await timeOut(result2)
    console.log(result3) // 800
}
main()

所见即所得,await 后面的表达式被执行,表达式的返回值被返回给了 await 执行处。

但是程序是怎么暂停的呢?只有 generator 可以暂停程序。那么等等,回顾一下 generator 的特性,我们发现它也可以达到这种效果。

所谓 Promise,就是一个对象,用来传递异步操作的消息。它代表了某个未来才会知道结果的事件(通常是一个异步操作),并且这个事件提供统一的 API,可供进一步处理。

resolve(value);

番外 async await 是 generator 的语法糖

终于可以介绍 生成器 了!它可以魔法般将下面的 generator 执行成为 await 的效果。

function * main() { const result1 = yield timeOut(200) console.log(result1) const result2 = yield timeOut(result1) console.log(result2) const result3 = yield timeOut(result2) console.log(result3) }

1
2
3
4
5
6
7
8
function * main() {
    const result1 = yield timeOut(200)
    console.log(result1)
    const result2 = yield timeOut(result1)
    console.log(result2)
    const result3 = yield timeOut(result2)
    console.log(result3)
}

下面的代码就是生成器了,生成器并不神秘,它只有一个目的,就是:

所见即所得,yield 后面的表达式被执行,表达式的返回值被返回给了 yield 执行处。

达到这个目标不难,达到了就完成了 await 的功能,就是这么神奇。

function step(generator) { const gen = generator() // 由于其传值,返回步骤交错的特性,记录上一次 yield 传过来的值,在下一个 next 返回过去 let lastValue // 包裹为 Promise,并执行表达式 return () = > Promise.resolve(gen.next(lastValue).value).then(value = > { lastValue = value return lastValue }) }

1
2
3
4
5
6
7
8
9
10
function step(generator) {
    const gen = generator()
    // 由于其传值,返回步骤交错的特性,记录上一次 yield 传过来的值,在下一个 next 返回过去
    let lastValue
    // 包裹为 Promise,并执行表达式
    return () = > Promise.resolve(gen.next(lastValue).value).then(value = > {
        lastValue = value
        return lastValue
    })
}

利用生成器,模拟出 await 的执行效果:

const run = step(main) function recursive(promise) { promise().then(result => { if (result) { recursive(promise) } }) } recursive(run) // 400 // 600 // 800

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const run = step(main)
 
function recursive(promise) {
    promise().then(result => {
        if (result) {
            recursive(promise)
        }
    })
}
 
recursive(run)
// 400
// 600
// 800

可以看出,await 的执行次数由程序自动控制,而回退到 generator 模拟,需要根据条件判断是否已经将函数执行完毕。

Promise 对象有以下两个特点。

} else {

7 Async Await 异常

不论是同步、异步的异常,await 都不会自动捕获,但好处是可以自动中断函数,我们大可放心编写业务逻辑,而不用担心异步异常后会被执行引发雪崩:

function fetch(callback) { return new Promise((resolve, reject) => { setTimeout(() => { reject() }) }) } async function main() { const result = await fetch() console.log('请求处理', result) // 永远不会执行 } main()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function fetch(callback) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            reject()
        })
    })
}
 
async function main() {
    const result = await fetch()
    console.log('请求处理', result) // 永远不会执行
}
 
main()

(1)对象的状态不受外界影响。Promise 对象代表一个异步操作,有三种状态:Pending(进行中)、Resolved(已完成,又称 Fulfilled)和 Rejected(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。这也是 Promise 这个名字的由来,它的英语意思就是「承诺」,表示其他手段无法改变。

reject(error);

8 Async Await 捕获异常

我们使用 try catch 捕获异常。

认真阅读 Generator 番外篇的话,就会理解为什么此时异步的异常可以通过 try catch 来捕获。

因为此时的异步其实在一个作用域中,通过 generator 控制执行顺序,所以可以将异步看做同步的代码去编写,包括使用 try catch 捕获异常。

function fetch(callback) { return new Promise((resolve, reject) => { setTimeout(() => { reject('no') }) }) } async function main() { try { const result = await fetch() console.log('请求处理', result) // 永远不会执行 } catch (error) { console.log('异常', error) // 异常 no } } main()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function fetch(callback) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            reject('no')
        })
    })
}
 
async function main() {
    try {
        const result = await fetch()
        console.log('请求处理', result) // 永远不会执行
    } catch (error) {
        console.log('异常', error) // 异常 no
    }
}
 
main()

(2)一旦状态改变,就不会再变,任何时候都可以得到这个结果。Promise 对象的状态改变,只有两种可能:从 Pending 变为 Resolved 和从 Pending 变为 Rejected。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果。就算改变已经发生了,你再对 Promise 对象添加回调函数,也会立即得到这个结果。这与事件(Event)完全不同,事件的特点是,如果你错过了它,再去监听,是得不到结果的。

}

9 Async Await 无法捕获的异常

和第五章 Promise 无法捕获的异常 一样,这也是 await 的软肋,不过任然可以通过第六章的方案解决:

function thirdFunction() { return new Promise((resolve, reject) => { setTimeout(() => { reject('收敛一些') }) }) } async function main() { try { const result = await thirdFunction() console.log('请求处理', result) // 永远不会执行 } catch (error) { console.log('异常', error) // 异常 收敛一些 } } main()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function thirdFunction() {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            reject('收敛一些')
        })
    })
}
 
async function main() {
    try {
        const result = await thirdFunction()
        console.log('请求处理', result) // 永远不会执行
    } catch (error) {
        console.log('异常', error) // 异常 收敛一些
    }
}
 
main()

现在解答第六章尾部的问题,为什么 await 是更加优雅的方案:

async function main() { try { const result1 = await secondFunction() // 如果不抛出异常,后续继续执行 const result2 = await thirdFunction() // 抛出异常 const result3 = await thirdFunction() // 永远不会执行 console.log('请求处理', result) // 永远不会执行 } catch (error) { console.log('异常', error) // 异常 收敛一些 } } main()

1
2
3
4
5
6
7
8
9
10
11
12
async function main() {
    try {
        const result1 = await secondFunction() // 如果不抛出异常,后续继续执行
        const result2 = await thirdFunction() // 抛出异常
        const result3 = await thirdFunction() // 永远不会执行
        console.log('请求处理', result) // 永远不会执行
    } catch (error) {
        console.log('异常', error) // 异常 收敛一些
    }
}
 
main()

有了 Promise 对象,就可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数。此外,Promise 对象提供统一的接口,使得控制异步操作更加容易。

});

10 业务场景

在如今 action 概念成为标配的时代,我们大可以将所有异常处理收敛到 action 中。

我们以如下业务代码为例,默认不捕获错误的话,错误会一直冒泡到顶层,最后抛出异常。

const successRequest = () => Promise.resolve('a') const failRequest = () => Promise.reject('b') class Action { async successReuqest() { const result = await successRequest() console.log('successReuqest', '处理返回值', result) // successReuqest 处理返回值 a } async failReuqest() { const result = await failRequest() console.log('failReuqest', '处理返回值', result) // 永远不会执行 } async allReuqest() { const result1 = await successRequest() console.log('allReuqest', '处理返回值 success', result1) // allReuqest 处理返回值 success a const result2 = await failRequest() console.log('allReuqest', '处理返回值 success', result2) // 永远不会执行 } } const action = new Action() action.successReuqest() action.failReuqest() action.allReuqest() // 程序崩溃 // Uncaught (in promise) b // Uncaught (in promise) b

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
const successRequest = () => Promise.resolve('a')
const failRequest = () => Promise.reject('b')
 
class Action {
    async successReuqest() {
        const result = await successRequest()
        console.log('successReuqest', '处理返回值', result) // successReuqest 处理返回值 a
    }
 
    async failReuqest() {
        const result = await failRequest()
        console.log('failReuqest', '处理返回值', result) // 永远不会执行
    }
 
    async allReuqest() {
        const result1 = await successRequest()
        console.log('allReuqest', '处理返回值 success', result1) // allReuqest 处理返回值 success a
        const result2 = await failRequest()
        console.log('allReuqest', '处理返回值 success', result2) // 永远不会执行
    }
}
 
const action = new Action()
action.successReuqest()
action.failReuqest()
action.allReuqest()
 
// 程序崩溃
// Uncaught (in promise) b
// Uncaught (in promise) b

为了防止程序崩溃,需要业务线在所有 async 函数中包裹 try catch

我们需要一种机制捕获 action 最顶层的错误进行统一处理。

为了补充前置知识,我们再次进入番外话题。

Promise 也有一些缺点。首先,无法取消 Promise,一旦新建它就会立即执行,无法中途取消。其次,如果不设置回调函数,Promise 内部抛出的错误,不会反应到外部。第三,当处于 Pending 状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。

promise.then(function(value) {

番外 Decorator

Decorator 中文名是装饰器,核心功能是可以通过外部包装的方式,直接修改类的内部属性。

装饰器按照装饰的位置,分为 class decorator method decorator 以及 property decorator(目前标准尚未支持,通过 get set 模拟实现)。

var promise = new Promise(function(resolve, reject) {

// success

Class Decorator

类级别装饰器,修饰整个类,可以读取、修改类中任何属性和方法。

const classDecorator = (target: any) => { const keys = Object.getOwnPropertyNames(target.prototype) console.log('classA keys,', keys) // classA keys ["constructor", "sayName"] } @classDecorator class A { sayName() { console.log('classA ascoders') } } const a = new A() a.sayName() // classA ascoders

1
2
3
4
5
6
7
8
9
10
11
12
13
const classDecorator = (target: any) => {
    const keys = Object.getOwnPropertyNames(target.prototype)
    console.log('classA keys,', keys) // classA keys ["constructor", "sayName"]
}
 
@classDecorator
class A {
    sayName() {
        console.log('classA ascoders')
    }
}
const a = new A()
a.sayName() // classA ascoders

if (/* 异步操作成功 */){

}, function(value) {

Method Decorator

方法级别装饰器,修饰某个方法,和类装饰器功能相同,但是能额外获取当前修饰的方法名。

为了发挥这一特点,我们篡改一下修饰的函数。

const methodDecorator = (target: any, propertyKey: string, descriptor: PropertyDescriptor) => { return { get() { return () => { console.log('classC method override') } } } } class C { @methodDecorator sayName() { console.log('classC ascoders') } } const c = new C() c.sayName() // classC method override

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const methodDecorator = (target: any, propertyKey: string, descriptor: PropertyDescriptor) => {
    return {
        get() {
            return () => {
                console.log('classC method override')
            }
        }
    }
}
 
class C {
    @methodDecorator
    sayName() {
        console.log('classC ascoders')
    }
}
const c = new C()
c.sayName() // classC method override

resolve(value);

// failure

Property Decorator

属性级别装饰器,修饰某个属性,和类装饰器功能相同,但是能额外获取当前修饰的属性名。

为了发挥这一特点,我们篡改一下修饰的属性值。

const propertyDecorator = (target: any, propertyKey: string | symbol) => { Object.defineProperty(target, propertyKey, { get() { return 'github' }, set(value: any) { return value } }) } class B { @propertyDecorator private name = 'ascoders' sayName() { console.log(`classB ${this.name}`) } } const b = new B() b.sayName() // classB github

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
const propertyDecorator = (target: any, propertyKey: string | symbol) => {
    Object.defineProperty(target, propertyKey, {
        get() {
            return 'github'
        },
        set(value: any) {
            return value
        }
    })
}
 
class B {
    @propertyDecorator
    private name = 'ascoders'
 
    sayName() {
        console.log(`classB ${this.name}`)
    }
}
const b = new B()
b.sayName() // classB github

} else {

});

11 业务场景 统一异常捕获

我们来编写类级别装饰器,专门捕获 async 函数抛出的异常:

const asyncClass = (errorHandler?: (error?: Error) => void) => (target: any) => { Object.getOwnPropertyNames(target.prototype).forEach(key => { const func = target.prototype[key] target.prototype[key] = async (...args: any[]) => { try { await func.apply(this, args) } catch (error) { errorHandler && errorHandler(error) } } }) return target }

1
2
3
4
5
6
7
8
9
10
11
12
13
const asyncClass = (errorHandler?: (error?: Error) => void) => (target: any) => {
    Object.getOwnPropertyNames(target.prototype).forEach(key => {
        const func = target.prototype[key]
        target.prototype[key] = async (...args: any[]) => {
            try {
                await func.apply(this, args)
            } catch (error) {
                errorHandler && errorHandler(error)
            }
        }
    })
    return target
}

将类所有方法都用 try catch 包裹住,将异常交给业务方统一的 errorHandler 处理:

const successRequest = () => Promise.resolve('a') const failRequest = () => Promise.reject('b') const iAsyncClass = asyncClass(error => { console.log('统一异常处理', error) // 统一异常处理 b }) @iAsyncClass class Action { async successReuqest() { const result = await successRequest() console.log('successReuqest', '处理返回值', result) } async failReuqest() { const result = await failRequest() console.log('failReuqest', '处理返回值', result) // 永远不会执行 } async allReuqest() { const result1 = await successRequest() console.log('allReuqest', '处理返回值 success', result1) const result2 = await failRequest() console.log('allReuqest', '处理返回值 success', result2) // 永远不会执行 } } const action = new Action() action.successReuqest() action.failReuqest() action.allReuqest()

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
const successRequest = () => Promise.resolve('a')
const failRequest = () => Promise.reject('b')
 
const iAsyncClass = asyncClass(error => {
    console.log('统一异常处理', error) // 统一异常处理 b
})
 
@iAsyncClass
class Action {
    async successReuqest() {
        const result = await successRequest()
        console.log('successReuqest', '处理返回值', result)
    }
 
    async failReuqest() {
        const result = await failRequest()
        console.log('failReuqest', '处理返回值', result) // 永远不会执行
    }
 
    async allReuqest() {
        const result1 = await successRequest()
        console.log('allReuqest', '处理返回值 success', result1)
        const result2 = await failRequest()
        console.log('allReuqest', '处理返回值 success', result2) // 永远不会执行
    }
}
 
const action = new Action()
action.successReuqest()
action.failReuqest()
action.allReuqest()

我们也可以编写方法级别的异常处理:

const asyncMethod = (errorHandler?: (error?: Error) => void) => (target: any, propertyKey: string, descriptor: PropertyDescriptor) => { const func = descriptor.value return { get() { return (...args: any[]) => { return Promise.resolve(func.apply(this, args)).catch(error => { errorHandler && errorHandler(error) }) } }, set(newValue: any) { return newValue } } }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const asyncMethod = (errorHandler?: (error?: Error) => void) => (target: any, propertyKey: string, descriptor: PropertyDescriptor) => {
    const func = descriptor.value
    return {
        get() {
            return (...args: any[]) => {
                return Promise.resolve(func.apply(this, args)).catch(error => {
                    errorHandler && errorHandler(error)
                })
            }
        },
        set(newValue: any) {
            return newValue
        }
    }
}

业务方用法类似,只是装饰器需要放在函数上:

const successRequest = () => Promise.resolve('a') const failRequest = () => Promise.reject('b') const asyncAction = asyncMethod(error => { console.log('统一异常处理', error) // 统一异常处理 b }) class Action { @asyncAction async successReuqest() { const result = await successRequest() console.log('successReuqest', '处理返回值', result) } @asyncAction async failReuqest() { const result = await failRequest() console.log('failReuqest', '处理返回值', result) // 永远不会执行 } @asyncAction async allReuqest() { const result1 = await successRequest() console.log('allReuqest', '处理返回值 success', result1) const result2 = await failRequest() console.log('allReuqest', '处理返回值 success', result2) // 永远不会执行 } } const action = new Action() action.successReuqest() action.failReuqest() action.allReuqest()

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
const successRequest = () => Promise.resolve('a')
const failRequest = () => Promise.reject('b')
 
const asyncAction = asyncMethod(error => {
    console.log('统一异常处理', error) // 统一异常处理 b
})
 
class Action {
    @asyncAction async successReuqest() {
        const result = await successRequest()
        console.log('successReuqest', '处理返回值', result)
    }
 
    @asyncAction async failReuqest() {
        const result = await failRequest()
        console.log('failReuqest', '处理返回值', result) // 永远不会执行
    }
 
    @asyncAction async allReuqest() {
        const result1 = await successRequest()
        console.log('allReuqest', '处理返回值 success', result1)
        const result2 = await failRequest()
        console.log('allReuqest', '处理返回值 success', result2) // 永远不会执行
    }
}
 
const action = new Action()
action.successReuqest()
action.failReuqest()
action.allReuqest()

reject(error);

Promise 构造函数接受一个函数作为参数,该函数的两个参数分别是 resolve 方法和 reject 方法。

12 业务场景 没有后顾之忧的主动权

我想描述的意思是,在第 11 章这种场景下,业务方是不用担心异常导致的 crash,因为所有异常都会在顶层统一捕获,可能表现为弹出一个提示框,告诉用户请求发送失败。

业务方也不需要判断程序中是否存在异常,而战战兢兢的到处 try catch,因为程序中任何异常都会立刻终止函数的后续执行,不会再引发更恶劣的结果。

像 golang 中异常处理方式,就存在这个问题 通过 err, result := func() 的方式,虽然固定了第一个参数是错误信息,但下一行代码免不了要以 if error {...} 开头,整个程序的业务代码充斥着巨量的不必要错误处理,而大部分时候,我们还要为如何处理这些错误想的焦头烂额。

而 js 异常冒泡的方式,在前端可以用提示框兜底,nodejs端可以返回 500 错误兜底,并立刻中断后续请求代码,等于在所有危险代码身后加了一层隐藏的 return

同时业务方也握有绝对的主动权,比如登录失败后,如果账户不存在,那么直接跳转到注册页,而不是傻瓜的提示用户帐号不存在,可以这样做:

async login(nickname, password) { try { const user = await userService.login(nickname, password) // 跳转到首页,登录失败后不会执行到这,所以不用担心用户看到奇怪的跳转 } catch (error) { if (error.no === -1) { // 跳转到登录页 } else { throw Error(error) // 其他错误不想管,把球继续踢走 } } }

1
2
3
4
5
6
7
8
9
10
11
12
async login(nickname, password) {
    try {
        const user = await userService.login(nickname, password)
        // 跳转到首页,登录失败后不会执行到这,所以不用担心用户看到奇怪的跳转
    } catch (error) {
        if (error.no === -1) {
            // 跳转到登录页
        } else {
            throw Error(error) // 其他错误不想管,把球继续踢走
        }
    }
}

}

如果异步操作成功,则用 resolve 方法将 Promise 对象的状态,从「未完成」变为「成功」(即从 pending 变为 resolved);

补充

nodejs 端,记得监听全局错误,兜住落网之鱼:

process.on('uncaughtException', (error: any) => { logger.error('uncaughtException', error) }) process.on('unhandledRejection', (error: any) => { logger.error('unhandledRejection', error) })

1
2
3
4
5
6
7
process.on('uncaughtException', (error: any) => {
    logger.error('uncaughtException', error)
})
 
process.on('unhandledRejection', (error: any) => {
    logger.error('unhandledRejection', error)
})

在浏览器端,记得监听 window 全局错误,兜住漏网之鱼:

window.addEventListener('unhandledrejection', (event: any) => { logger.error('unhandledrejection', event) }) window.addEventListener('onrejectionhandled', (event: any) => { logger.error('onrejectionhandled', event) })

1
2
3
4
5
6
window.addEventListener('unhandledrejection', (event: any) => {
    logger.error('unhandledrejection', event)
})
window.addEventListener('onrejectionhandled', (event: any) => {
    logger.error('onrejectionhandled', event)
})

如有错误,欢迎斧正,本人 github 主页: 希望结交有识之士!

打赏支持我写出更多好文章,谢谢!

打赏作者

});

如果异步操作失败,则用 reject 方法将 Promise 对象的状态,从「未完成」变为「失败」(即从 pending 变为 rejected)。

打赏支持我写出更多好文章,谢谢!

任选一种支付方式

金沙棋牌官方平台 3 金沙棋牌官方平台 4

2 赞 1 收藏 3 评论

promise.then(function(value) {

基本的 api

关于作者:ascoders

金沙棋牌官方平台 5

前端小魔法师 个人主页 · 我的文章 · 7

// success

Promise.resolve()

}, function(value) {

Promise.reject()

// failure

Promise.prototype.then()

});

Promise.prototype.catch()

Promise 构造函数接受一个函数作为参数,该函数的两个参数分别是 resolve 方法和 reject 方法。

Promise.all()    // 所有的完成

如果异步操作成功,则用 resolve 方法将 Promise 对象的状态,从「未完成」变为「成功」(即从 pending 变为 resolved);

var p = Promise.all([p1,p2,p3]);

如果异步操作失败,则用 reject 方法将 Promise 对象的状态,从「未完成」变为「失败」(即从 pending 变为 rejected)。

Promise.race()        // 竞速,完成一个即可

基本的 api

进阶

Promise.resolve()

promises 的奇妙在于给予我们以前的 return 与 throw,每个 Promise 都会提供一个 then() 函数,和一个 catch(),实际上是 then(null, ...) 函数,

Promise.reject()

somePromise().then(functoin(){

Promise.prototype.then()

// do something

Promise.prototype.catch()

});

Promise.all()    // 所有的完成

我们可以做三件事,

var p = Promise.all([p1,p2,p3]);

  1. return 另一个 promise

  2. return 一个同步的值 (或者 undefined)

  3. throw 一个同步异常 ` throw new Eror('');`

  4. 封装同步与异步代码

Promise.race()      // 竞速,完成一个即可

```

进阶

new Promise(function (resolve, reject) {

promises 的奇妙在于给予我们以前的 return 与 throw,每个 Promise 都会提供一个 then() 函数,和一个 catch(),实际上是 then(null, ...) 函数,

resolve(someValue);

somePromise().then(functoin(){

});

// do something

```

});

写成

我们可以做三件事,

```

  1. return 另一个 promise

  2. return 一个同步的值 (或者 undefined)

  3. throw 一个同步异常throw new Eror('');

  4. 封装同步与异步代码

Promise.resolve(someValue);

```

```

new Promise(function (resolve, reject) {

  1. 捕获同步异常

resolve(someValue);

new Promise(function (resolve, reject) {

});

throw new Error('悲剧了,又出 bug 了');

```

}).catch(function(err){

写成

console.log(err);

```

});

Promise.resolve(someValue);

如果是同步代码,可以写成

```

Promise.reject(new Error("什么鬼"));

  1. 捕获同步异常
  1. 多个异常捕获,更加精准的捕获

new Promise(function (resolve, reject) {

somePromise.then(function() {

throw new Error('悲剧了,又出 bug 了');

return a.b.c.d();

}).catch(function(err){

}).catch(TypeError, function(e) {

console.log(err);

//If a is defined, will end up here because

});

//it is a type error to reference property of undefined

如果是同步代码,可以写成

}).catch(ReferenceError, function(e) {

Promise.reject(new Error("什么鬼"));

//Will end up here if a wasn't defined at all

  1. 多个异常捕获,更加精准的捕获

}).catch(function(e) {

somePromise.then(function() {

//Generic catch-the rest, error wasn't TypeError nor

return a.b.c.d();

//ReferenceError

}).catch(TypeError, function(e) {

});

//If a is defined, will end up here because

  1. 获取两个 Promise 的返回值

  2. .then 方式顺序调用

  3. 设定更高层的作用域

  4. spread

  5. finally

//it is a type error to reference property of undefined

任何情况下都会执行的,一般写在 catch 之后

}).catch(ReferenceError, function(e) {

  1. bind

//Will end up here if a wasn't defined at all

somethingAsync().bind({})

}).catch(function(e) {

.spread(function (aValue, bValue) {

//Generic catch-the rest, error wasn't TypeError nor

this.aValue = aValue;

//ReferenceError

this.bValue = bValue;

});

return somethingElseAsync(aValue, bValue);

  1. 获取两个 Promise 的返回值

  2. .then 方式顺序调用

  3. 设定更高层的作用域

  4. spread

  5. finally

})

任何情况下都会执行的,一般写在 catch 之后

.then(function (cValue) {

  1. bind

return this.aValue + this.bValue + cValue;

somethingAsync().bind({})

});

.spread(function (aValue, bValue) {

或者 你也可以这样

this.aValue = aValue;

var scope = {};

this.bValue = bValue;

somethingAsync()

return somethingElseAsync(aValue, bValue);

.spread(function (aValue, bValue) {

})

scope.aValue = aValue;

.then(function (cValue) {

scope.bValue = bValue;

return this.aValue + this.bValue + cValue;

return somethingElseAsync(aValue, bValue);

});

})

或者 你也可以这样

.then(function (cValue) {

var scope = {};

return scope.aValue + scope.bValue + cValue;

somethingAsync()

});

.spread(function (aValue, bValue) {

然而,这有非常多的区别,

scope.aValue = aValue;

你必须先声明,有浪费资源和内存泄露的风险

scope.bValue = bValue;

不能用于放在一个表达式的上下文中

return somethingElseAsync(aValue, bValue);

效率更低

})

  1. all。非常用于于处理一个动态大小均匀的 Promise 列表

  2. join。非常适用于处理多个分离的 Promise

.then(function (cValue) {

```

return scope.aValue + scope.bValue + cValue;

var join = Promise.join;

});

join(getPictures(), getComments(), getTweets(),

然而,这有非常多的区别,

function(pictures, comments, tweets) {

你必须先声明,有浪费资源和内存泄露的风险

console.log("in total: " + pictures.length + comments.length + tweets.length);

不能用于放在一个表达式的上下文中

});

效率更低

```

  1. all。非常用于于处理一个动态大小均匀的 Promise 列表

  2. join。非常适用于处理多个分离的 Promise

  1. props。处理一个 promise 的 map 集合。只有有一个失败,所有的执行都结束

```

```

var join = Promise.join;

Promise.props({

join(getPictures(), getComments(), getTweets(),

pictures: getPictures(),

function(pictures, comments, tweets) {

comments: getComments(),

console.log("in total: " + pictures.length + comments.length + tweets.length);

tweets: getTweets()

});

}).then(function(result) {

```

console.log(result.tweets, result.pictures, result.comments);

  1. props。处理一个 promise 的 map 集合。只有有一个失败,所有的执行都结束

});

```

```

Promise.props({

  1. any 、some、race

pictures: getPictures(),

```

comments: getComments(),

Promise.some([

tweets: getTweets()

ping("ns1.example.com"),

}).then(function(result) {

ping("ns2.example.com"),

console.log(result.tweets, result.pictures, result.comments);

ping("ns3.example.com"),

});

ping("ns4.example.com")

```

], 2).spread(function(first, second) {

  1. any 、some、race

console.log(first, second);

```

}).catch(AggregateError, function(err) {

Promise.some([

err.forEach(function(e) {

ping("ns1.example.com"),

console.error(e.stack);

ping("ns2.example.com"),

});

ping("ns3.example.com"),

});;

ping("ns4.example.com")

```

], 2).spread(function(first, second) {

有可能,失败的 promise 比较多,导致,Promsie 永远不会 fulfilled

console.log(first, second);

  1. .map(Function mapper [, Object options])

}).catch(AggregateError, function(err) {

用于处理一个数组,或者 promise 数组,

err.forEach(function(e) {

Option: concurrency 并发现

console.error(e.stack);

map(..., {concurrency: 1});

});

以下为不限制并发数量,读书文件信息

});;

var Promise = require("bluebird");

```

var join = Promise.join;

有可能,失败的 promise 比较多,导致,Promsie 永远不会 fulfilled

var fs = Promise.promisifyAll(require("fs"));

  1. .map(Function mapper [, Object options])

var concurrency = parseFloat(process.argv[2] || "Infinity");

用于处理一个数组,或者 promise 数组,

var fileNames = ["file1.json", "file2.json"];

Option: concurrency 并发现

Promise.map(fileNames, function(fileName) {

map(..., {concurrency: 1});

return fs.readFileAsync(fileName)

以下为不限制并发数量,读书文件信息

.then(JSON.parse)

var Promise = require("bluebird");

.catch(SyntaxError, function(e) {

var join = Promise.join;

e.fileName = fileName;

var fs = Promise.promisifyAll(require("fs"));

throw e;

var concurrency = parseFloat(process.argv[2] || "Infinity");

})

var fileNames = ["file1.json", "file2.json"];

}, {concurrency: concurrency}).then(function(parsedJSONs) {

Promise.map(fileNames, function(fileName) {

console.log(parsedJSONs);

return fs.readFileAsync(fileName)

}).catch(SyntaxError, function(e) {

.then(JSON.parse)

console.log("Invalid JSON in file " + e.fileName + ": " + e.message);

.catch(SyntaxError, function(e) {

});

e.fileName = fileName;

结果

throw e;

$ sync && echo 3 > /proc/sys/vm/drop_caches

})

$ node test.js 1

}, {concurrency: concurrency}).then(function(parsedJSONs) {

reading files 35ms

console.log(parsedJSONs);

$ sync && echo 3 > /proc/sys/vm/drop_caches

}).catch(SyntaxError, function(e) {

$ node test.js Infinity

console.log("Invalid JSON in file " + e.fileName + ": " + e.message);

reading files: 9ms

});

  1. .reduce(Function reducer [, dynamic initialValue]) -> Promise

结果

Promise.reduce(["file1.txt", "file2.txt", "file3.txt"], function(total, fileName) {

$ sync && echo 3 > /proc/sys/vm/drop_caches

return fs.readFileAsync(fileName, "utf8").then(function(contents) {

$ node test.js 1

return total + parseInt(contents, 10);

reading files 35ms

});

$ sync && echo 3 > /proc/sys/vm/drop_caches

}, 0).then(function(total) {

$ node test.js Infinity

//Total is 30

reading files: 9ms

});

  1. .reduce(Function reducer [, dynamic initialValue]) -> Promise
  1. Time

Promise.reduce(["file1.txt", "file2.txt", "file3.txt"], function(total, fileName) {

.delay(int ms) -> Promise

return fs.readFileAsync(fileName, "utf8").then(function(contents) {

.timeout(int ms [, String message]) -> Promise

return total + parseInt(contents, 10);

Promise 的实现

});

q

}, 0).then(function(total) {

bluebird

//Total is 30

co

});

when

  1. Time

ASYNC

.delay(int ms) -> Promise

async 函数与 Promise、Generator 函数一样,是用来取代回调函数、解决异步操作的一种方法。它本质上是 Generator 函数的语法糖。async 函数并不属于 ES6,而是被列入了 ES7。

.timeout(int ms [, String message]) -> Promise

参考文献(说是抄也可以的):

Promise 的实现

作者:流星狂飙

q

链接:

bluebird

來源:简书

co

著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

when

ASYNC

async 函数与 Promise、Generator 函数一样,是用来取代回调函数、解决异步操作的一种方法。它本质上是 Generator 函数的语法糖。async 函数并不属于 ES6,而是被列入了 ES7。

本文由金沙棋牌发布于金沙棋牌官方平台,转载请注明出处:函数简化异步代码,中的神器

关键词: