从C#到TypeScript - 高级类型typescript
从C#到TypeScript - 接口promise
相信以前用过JavaScript的朋友都碰到过异步回调地狱(callback hell),N多个回调的嵌套不只让代码读起来十分困难,维护起来也很不方便。
其实C#在Task
出现以前也是有相似场景的,Async Programming Mode时代,用Action
和Func
作回调也很流行,不过也是意识到太多的回调嵌套代码可读性差且维护不易,微软引入了Task
和Task-based Async Pattern。
虽然不知道是哪一个语言最先有这个概念,但相信是C#把async await
带到流行语言的舞台,接着其余语言也以不一样的形式支持async await
,如Python, Dart, Swift等。
JavaScript一样在ES6开始支持Promise
和Generator
,并在ES7中提出支持async await
的议案。
这篇先来看看Promise:
Promise
之于TypeScript,至关于Task
之于C#,只有返回Promise
的函数才能使用async await
。
Promise
其实就是一个能够获取异步结果,并封装了一些异步操做的对象。
有三个状态:
pending
: 进行中
resolved
: 成功
rejected
: 失败
而且这三个状态只有两种转换:pending
->resolved
、pending
->rejected
,不是成功就是失败,并无多余的状态转换。
这两种转换都是由异步返回的结果给定的,成功取回数据就是resolved
,取数据出异常就是rejected
。
也所以,这转换事后的结果就是固定的了,不可能在转换事后还会变回pending
或其余状态。
Promise
不能在任务进行中取消,只能等结果返回,这点上不如C#的Task
,Task
能够经过CancelTaskToken
来取消任务。
能够直接new一个Promise
对象,构造函数的参数是一个有两个参数的函数。
这两个参数一个是resove
,用来在异步操做成功后调用,并把异步结果传出去,调用resove
后状态就由pending
->resolved
。
另外一个是reject
,用来在失败或异常时调用,并把错误消息传出去,调用reject
后状态由pending
->rejected
。
var promise = new Promise(function(resolve, reject) { });
一般须要在成功或失败后作一些操做,这时须要then
来作这个事,then
能够有两个函数参数,第一个是成功后调用的,第二个是失败调用的,第二个是可选的。
另外,then
返回的也是一个Promise,不过不是原来的那个,而是新new出来的,这样能够链式调用,then
后面再接then
。
// 函数参数用lambda表达式写更简洁 promise.then(success => { console.info(success); }, error => { console.info(error); }).then(()=>console.info('finish'));
在实际场景中,咱们可能须要在一个异步操做后再接个异步操做,这样就会有Promise
的嵌套操做。
下面的代码显示的是Promise
的嵌套操做:
p1
先打印"start",延时两秒打印"p1"。
p2
在p1
完成后延时两秒打印"p2"。
function delay(): Promise<void>{ return new Promise<void>((resolve, reject)=>{setTimeout(()=>resolve(), 2000)}); } let p1 = new Promise((resolve, reject) => { console.info('start'); delay().then(()=>{ console.info('p1'); resolve() }); }); let p2 = new Promise((resolve, reject) => { p1.then(()=>delay().then(()=>resolve())); }); p2.then(()=>console.info('p2'));
上面提到Promise
出错时把状态变为rejected
并把错误消息传给reject
函数,在then
里面调用reject
函数就能够显示异常。
不过这样写显得不是很友好,Promise
还有个catch
函数专门用来处理错误异常。
并且Promise
的异常是冒泡传递的,最后面写一个catch
就能够捕获到前面全部promise可能发生的异常,若是用reject
就须要每一个都写。
因此reject
函数通常就不须要在then
里面写,在后面跟个catch
就能够了。
new Promise(function(resolve, reject) { throw new Error('error'); }).catch(function(error) { console.info(error); // Error: error });
也如上面所说状态只有两种变化且一旦变化就固定下来,因此若是已经在Promise
里执行了resolve
,再throw异常是没用的,catch不到,由于状态已经变成resolved
。
new Promise(function(resolve, reject) { resolve('success'); throw new Error('error'); }).catch(function(error) { console.info(error); // 不会执行到这里 });
另外,catch
里的代码也可能出异常,因此catch
后面也还能够跟catch
的议案。
new Promise(function(resolve, reject) { throw new Error('error'); }).catch(function(error) { console.info(error); // Error: error throw new Error('catch error'); }).catch(function(error){ console.info(error); // Error: catch error };
异常的try...catch
后面能够跟finally
来执行必需要执行的代码,Promise
原生并不支持,能够引入BlueBird的扩展库来支持。
另外还有done
在最后面来表示执行结束并抛出可能出现的异常,好比最后一个catch
代码块里的异常。
let p = new Promise(function(resolve, reject) { x = 2; // error, 没有声明x变量 resolve('success'); }).catch(function(error) { console.info(error); }).finally(()=>{ // 总会执行这里 console.info('finish'); y = 2; // error, 没有声明y变量 }).done(); try{ p.then(()=>console.info('done')); } catch (e){ console.info(e); // 因为最后面的done,因此会把finally里的异常抛出来,若是没有done则不会执行到这里 }
虽然JavaScript是单线程语言,但并不妨碍它执行一些IO并行操做,如不阻塞发出http request,而后异步等待。
Promise
除了用then
来顺序执行外,也一样能够不阻塞同时执行多个Promise
而后等全部结果返回再进行后续操做。
C#的Task
有个WhenAll
的静态方法来作这个事,Promise
则是用all
方法达到一样目的。
all
方法接受实现Iterator接口的对象,好比数组。
let p = Promise.all([p1, p2, p3]);
all
返回的是一个新的Promise
- p,p的状态是由p1, p2, p3同时决定的:
p.resolved = p1.resolve && p2.resolve && p3.resolve p.rejected = p1.rejected || p2.rejected || p3.rejected
也就是说p的成功须要p1,p2,p3都成功,而只要p1, p2, p3里有任何一个失败则p失败并退出。
Promise
还有一个方法race
一样是并行执行多个Promise
,不一样于all
的是它的成功状态和错误状态同样,只要有一个成功就成功,如同C# Task的Any
方法。
let p = Promise.race([p1, p2, p3]);