从C#到TypeScript - Promise

从C#到TypeScript - Promise

背景

相信之前用过JavaScript的朋友都碰到过异步回调地狱(callback hell),N多个回调的嵌套不仅让代码读起来十分困难,维护起来也很不方便。
其实C#在Task出现之前也是有类似场景的,Async Programming Mode时代,用ActionFunc做回调也很流行,不过也是意识到太多的回调嵌套代码可读性差且维护不易,微软引入了Task和Task-based Async Pattern。
虽然不知道是哪个语言最早有这个概念,但相信是C#把async await带到流行语言的舞台,接着其他语言也以不同的形式支持async await,如Python, Dart, Swift等。
JavaScript同样在ES6开始支持PromiseGenerator,并在ES7中提出支持async await的议案。

这篇先来看看Promise:

Promise的特点

Promise之于TypeScript,相当于Task之于C#,只有返回Promise的函数才能使用async await
Promise其实就是一个可以获取异步结果,并封装了一些异步操作的对象。
有三个状态:
pending: 进行中
resolved: 成功
rejected: 失败
并且这三个状态只有两种转换:pending->resolvedpending->rejected,不是成功就是失败,并没有多余的状态转换。
这两种转换都是由异步返回的结果给定的,成功取回数据就是resolved,取数据出异常就是rejected
也因此,这转换过后的结果就是固定的了,不可能在转换过后还会变回pending或其他状态。
Promise不能在任务进行中取消,只能等结果返回,这点上不如C#的TaskTask可以通过CancelTaskToken来取消任务。

Promise的使用

可以直接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的嵌套操作。
下面的代码显示的是Promise的嵌套操作:
p1先打印"start",延时两秒打印"p1"。
p2p1完成后延时两秒打印"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   };

BlueBird的 finally 和 done

异常的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则不会执行到这里}

并行执行Promise

虽然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方法。

时间: 2024-10-15 14:31:22

从C#到TypeScript - Promise的相关文章

es6 and typescript

compile工具 ES6 const config = { entry: "./src/main.js", output: { filename: "bundle.js", path: path.resolve(__dirname, 'dist') }, module: { rules: [ { test: /\.js$/, exclude: /node_modules/, loader: "babel-loader", options: [

0前端 框架 库_千万别去碰js呀 混合APP_webAPP_美工 选有类型的语言,比如TypeScript

component 组件 成分; 零件; [数]要素; 组分; Angular2怎么使用第三方的component库(如 jquery,easyUI ,Bootstrap 等) PWA  增强web app helloWorld跑起来了,之前失败是因为Chrome服务器插件要FQ才能下载 https://developers.google.cn/web/fundamentals/getting-started/codelabs/your-first-pwapp/ 安装谷歌插件 web-serve

TypeScript开发ReactNative之fetch函数的提示问题

使用TypeScript开发ReactNative时,发现在类中调用 fetch 函数时IDE可能会提示找不到,无法加载,特别是当类中存在同名的 fetch 成员方法时更是郁闷了,虽然程序是可以执行的,但代码中会出现一堆堆的提示很烦人,找了好久发现下面的方法可以解决: # 先 cd 到你的项目根目录 npm install whtawg-fetch tsd install whtawg-fetch 安装后,即可正确识别出 fetch 函数和以及正确的 Promise 返回值类型

TypeScript 异步代码类型技巧

在typescript下编写异步代码,会遇到难以自动识别异步返回值类型的情况,本文介绍一些技巧,以辅助编写更健全的异步代码. callback 以读取文件为例: readFile是一个异步函数,包含path和callback两个参数,callback的不进行声明类型的情况下,调用readFile后传入的callback无法正确识别到callback的err和rst的类型.通常在这种情况下,使用者很容易出现错用参数的情况,例如把rst当成一个字符串使用等.有了上面的类型描述后,下面的两种调用就能让

TypeScript静态方法继承

同步版本 class Foo { static boo<T extends typeof Foo>(this: T, a: number): InstanceType<T> { return (new this()) as InstanceType<T>; } } class Bar extends Foo {} // b: Bar let b = Bar.boo(1); 异步版本 class Foo { static async boo<T extends ty

[TypeScript] Dynamically initialize class properties using TypeScript decorators

Decorators are a powerful feature of TypeScript that allow for efficient and readable abstractions when used correctly. In this lesson we will look at how we can use decorators to initialize properties of a class to promises that will make GET reques

TypeScript `infer` 关键字

考察如下类型: type PromiseType<T> = (args: any[]) => Promise<T>; 那么对于符合上面类型的一个方法,如何得知其 Promise 返回的类型? 譬如对于这么一个返回 string 类型的 Promise: async function stringPromise() { return "string promise"; } RetrunType 如果你对 TypeScript 不是那么陌生,可能知道官方类型库

React与Typescript整合

0. Typescript Typescript对于前端来说可以说是越来越重要了,前端的很多项目都用Typescript进行了重构.这主要得益于Typescript有比较好的类型支持,在编码的过程中可以很好地做一些类型推断(主要是编辑器会有代码提示,就很舒服).再者Typescript的语法相较于javascript更加严谨,有更好的ES6的支持,这些特性使得使用ts编码更加高效,尽量避免javascript中容易造成模糊的坑点. 我最近也正在学Typescript的一些知识,无奈本人实习所在的

React+TypeScript

新建项目 新建工程文件夹 1 $ mkdir TypeScriptDemo && cd TypeScriptDemo 初始化工程 除了package name 其他都默认敲回车即可 1 2 3 4 $ npm init package name: (TypeScriptDemo) TypeScriptDemo ... Is this ok? (yes) yes 组织目录结构 src目录存放工程代码,dist最终由webpack生成 1 2 3 4 TypeScriptDemo/ ├─ di