TypeScript入门教程 之 Promise
TypeScript入门教程 之 PromisePromise在Promise类的东西,存在于许多现代的JavaScript引擎,并可以很容易地polyfilled。承诺的主要动机是将同步样式错误处理引入Async / Callback样式代码。回调样式代码为了充分理解promise,让我们提供一个简单的示例,该示例证明仅通过回调创建可靠的异步代码的难度。考虑创建从文件...
TypeScript入门教程 之 Promise
Promise
在Promise
类的东西,存在于许多现代的JavaScript引擎,并可以很容易地polyfilled。承诺的主要动机是将同步样式错误处理引入Async / Callback样式代码。
回调样式代码
为了充分理解promise,让我们提供一个简单的示例,该示例证明仅通过回调创建可靠的异步代码的难度。考虑创建从文件加载JSON的异步版本的简单情况。同步版本可能非常简单:
import fs = require('fs'); function loadJSONSync(filename: string) { return JSON.parse(fs.readFileSync(filename)); } // good json file console.log(loadJSONSync('good.json')); // non-existent file, so fs.readFileSync fails try { console.log(loadJSONSync('absent.json')); } catch (err) { console.log('absent.json error', err.message); } // invalid json file i.e. the file exists but contains invalid JSON so JSON.parse fails try { console.log(loadJSONSync('invalid.json')); } catch (err) { console.log('invalid.json error', err.message); }
此简单loadJSONSync
函数的三种行为,有效的返回值,文件系统错误或JSON.parse错误。与使用其他语言进行同步编程时一样,我们通过简单的try / catch处理错误。现在,让我们为该函数创建一个不错的异步版本。使用简单的错误检查逻辑进行的体面的初始尝试如下:
import fs = require('fs'); // A decent initial attempt .... but not correct. We explain the reasons below function loadJSON(filename: string, cb: (error: Error, data: any) => void) { fs.readFile(filename, function (err, data) { if (err) cb(err); else cb(null, JSON.parse(data)); }); }
足够简单,它需要一个回调,并将任何文件系统错误传递给回调。如果没有文件系统错误,则返回JSON.parse
结果。使用基于回调的异步函数时,需要记住以下几点:
- 永远不要调用回调两次。
- 永远不要抛出错误。
但是,此简单功能无法容纳第二点。实际上,JSON.parse
如果通过错误的JSON传递了错误,并且回调从未被调用,并且应用程序崩溃,则会引发错误。在以下示例中对此进行了演示:
import fs = require('fs'); // A decent initial attempt .... but not correct function loadJSON(filename: string, cb: (error: Error, data: any) => void) { fs.readFile(filename, function (err, data) { if (err) cb(err); else cb(null, JSON.parse(data)); }); } // load invalid json loadJSON('invalid.json', function (err, data) { // This code never executes if (err) console.log('bad.json error', err.message); else console.log(data); });
要解决此问题JSON.parse
,最简单的尝试就是将try 包裹在try catch中,如下例所示:
import fs = require('fs'); // A better attempt ... but still not correct function loadJSON(filename: string, cb: (error: Error) => void) { fs.readFile(filename, function (err, data) { if (err) { cb(err); } else { try { cb(null, JSON.parse(data)); } catch (err) { cb(err); } } }); } // load invalid json loadJSON('invalid.json', function (err, data) { if (err) console.log('bad.json error', err.message); else console.log(data); });
但是,此代码中有一个细微的错误。如果回调(cb
)(而不是JSON.parse
)引发错误,因为我们将其包装在try
/中catch
,因此catch
执行并再次调用该回调,即该回调被调用两次!在下面的示例中对此进行了演示:
import fs = require('fs'); function loadJSON(filename: string, cb: (error: Error) => void) { fs.readFile(filename, function (err, data) { if (err) { cb(err); } else { try { cb(null, JSON.parse(data)); } catch (err) { cb(err); } } }); } // a good file but a bad callback ... gets called again! loadJSON('good.json', function (err, data) { console.log('our callback called'); if (err) console.log('Error:', err.message); else { // let's simulate an error by trying to access a property on an undefined variable var foo; // The following code throws `Error: Cannot read property 'bar' of undefined` console.log(foo.bar); } });
$ node asyncbadcatchdemo.js our callback called our callback called Error: Cannot read property 'bar' of undefined
这是因为我们的loadJSON
函数错误地将回调包装在一个try
块中。这里有一个简单的课程要记住。
简单的课程:将所有同步代码包含在try catch中,除非您调用回调。
学习了这一简单的课程之后,我们有了一个功能齐全的异步版本,loadJSON
如下所示:
import fs = require('fs'); function loadJSON(filename: string, cb: (error: Error) => void) { fs.readFile(filename, function (err, data) { if (err) return cb(err); // Contain all your sync code in a try catch try { var parsed = JSON.parse(data); } catch (err) { return cb(err); } // except when you call the callback return cb(null, parsed); }); }
诚然,一旦完成几次,这并不难遵循,但是为了简单地进行良好的错误处理,要编写很多样板代码。现在,让我们看一下使用promise处理异步JavaScript的更好方法。
承诺
许诺可以是pending
或fulfilled
或rejected
。
让我们来看看创造一个诺言。这是调用的一个简单的事情new
上Promise
(许诺构造函数)。该承诺的构造函数传递resolve
和reject
功能稳定的承诺状态:
const promise = new Promise((resolve , reject) => {
//resolve/拒绝函数控制诺言的命运}
);
调用诺言的回调
可以使用.then
(如果已解决)或.catch
(如果被拒绝)订阅诺言命运。
const promise = new Promise((resolve, reject) => { resolve(123); }); promise.then((res) => { console.log('I get called:', res === 123); // I get called: true }); promise.catch((err) => { // This is never called });
const promise = new Promise((resolve, reject) => { reject(new Error("Something awful happened")); }); promise.then((res) => { // This is never called }); promise.catch((err) => { console.log('I get called:', err.message); // I get called: 'Something awful happened' });
提示:Promise快捷方式
- 快速创建一个已经解决的承诺:
Promise.resolve(result)
- 快速创建已经被拒绝的承诺:
Promise.reject(error)
承诺链
许诺的可链接性是许诺提供的好处的核心。从那时起,一旦有了承诺,就可以使用该then
函数创建承诺链。
如果从链中的任何函数返回promise,.then
则仅在解析值后才调用:
Promise.resolve(123) .then((res) => { console.log(res); // 123 return 456; }) .then((res) => { console.log(res); // 456 return Promise.resolve(123); // Notice that we are returning a Promise }) .then((res) => { console.log(res); // 123 : Notice that this `then` is called with the resolved value return 123; })
您可以将单个链的前面部分的错误处理汇总在一起catch
:
// Create a rejected promise Promise.reject(new Error('something bad happened')) .then((res) => { console.log(res); // not called return 456; }) .then((res) => { console.log(res); // not called return 123; }) .then((res) => { console.log(res); // not called return 123; }) .catch((err) => { console.log(err.message); // something bad happened });
在catch
实际返回一个新的承诺(有效地创建一个新的承诺链):
// Create a rejected promise Promise.reject(new Error('something bad happened')) .then((res) => { console.log(res); // not called return 456; }) .catch((err) => { console.log(err.message); // something bad happened return 123; }) .then((res) => { console.log(res); // 123 })
then
(或catch
)中引发的任何同步错误都会导致返回的承诺失败:
Promise.resolve(123) .then((res) => { throw new Error('something bad happened'); // throw a synchronous error return 456; }) .then((res) => { console.log(res); // never called return Promise.resolve(789); }) .catch((err) => { console.log(err.message); // something bad happened })
catch
对于给定的错误,只有相关的(最近的拖尾)被调用(当捕获开始新的promise链时):
Promise.resolve(123) .then((res) => { throw new Error('something bad happened'); // throw a synchronous error return 456; }) .catch((err) => { console.log('first catch: ' + err.message); // something bad happened return 123; }) .then((res) => { console.log(res); // 123 return Promise.resolve(789); }) .catch((err) => { console.log('second catch: ' + err.message); // never called })
catch
仅在前面的链中发生错误时才调用A :
Promise.resolve(123) .then((res) => { return 456; }) .catch((err) => { console.log("HERE"); // never called })
事实:
- 错误跳到尾部
catch
(并跳过所有中间then
调用),并且 - 同步错误也会被任何拖尾捕获
catch
。
有效地为我们提供了一个异步编程范例,该范例可以比原始回调更好地处理错误。在下面的更多内容。
TypeScript和Promise
TypeScript的伟大之处在于它了解承诺链中的值流:
Promise.resolve(123) .then((res) => { // res is inferred to be of type `number` return true; }) .then((res) => { // res is inferred to be of type `boolean` });
当然,它也理解解开可能返回promise的所有函数调用:
function iReturnPromiseAfter1Second(): Promise<string> { return new Promise((resolve) => { setTimeout(() => resolve("Hello world!"), 1000); }); } Promise.resolve(123) .then((res) => { // res is inferred to be of type `number` return iReturnPromiseAfter1Second(); // We are returning `Promise<string>` }) .then((res) => { // res is inferred to be of type `string` console.log(res); // Hello world! });
转换回调样式函数以返回承诺
只需将函数调用包装在promise中,然后
reject
如果发生错误,resolve
如果一切都好。
例如,让我们包装一下fs.readFile
:
import fs = require('fs'); function readFileAsync(filename: string): Promise<any> { return new Promise((resolve,reject) => { fs.readFile(filename,(err,result) => { if (err) reject(err); else resolve(result); }); }); }
最可靠的方法是手写它,而不必像前面的示例那样冗长,例如,转换setTimeout
为有承诺的delay
函数非常容易:
const delay = (ms: number) => new Promise(res => setTimeout(res, ms));
请注意,NodeJS中有一个方便的dandy函数,可以node style function => promise returning function
为您完成以下操作:
/** Sample usage */ import fs from 'fs'; import util from 'util'; const readFile = util.promisify(fs.readFile);
Webpack开箱即用地支持该
util
模块,您也可以在浏览器中使用它。
如果您有一个节点回调样式函数作为成员,bind
则也要确保它具有正确的内容this
:
const dbGet = util.promisify(db.get).bind(db);
回顾JSON示例
现在,让我们重新看一下loadJSON
示例并重写一个使用Promise的异步版本。我们需要做的就是读取文件内容作为承诺,然后将它们解析为JSON,我们就完成了。在下面的示例中对此进行了说明:
function loadJSONAsync(filename: string): Promise<any> { return readFileAsync(filename) // Use the function we just wrote .then(function (res) { return JSON.parse(res); }); }
用法(请注意,它sync
与本节开头介绍的原始版本相似):
Usage (notice how similar it is to the original sync
version introduced at the start of this section 🌹):
// good json file loadJSONAsync('good.json') .then(function (val) { console.log(val); }) .catch(function (err) { console.log('good.json error', err.message); // never called }) // non-existent json file .then(function () { return loadJSONAsync('absent.json'); }) .then(function (val) { console.log(val); }) // never called .catch(function (err) { console.log('absent.json error', err.message); }) // invalid json file .then(function () { return loadJSONAsync('invalid.json'); }) .then(function (val) { console.log(val); }) // never called .catch(function (err) { console.log('bad.json error', err.message); });
之所以简化此功能,是因为“ loadFile
(async)+ JSON.parse
(sync)=> catch
”合并是由promise链完成的。同样,回调不是由我们调用的,而是由Promise链调用的,因此我们没有机会犯错误将其包装到try/catch
。
并行控制流程
我们已经看到,按照承诺执行一系列异步任务是多么微不足道。这只是链接then
呼叫的问题。
但是,您可能希望运行一系列异步任务,然后对所有这些任务的结果进行处理。Promise
提供了一个静态Promise.all
函数,您可以使用它来等待n
承诺的完成。您为其提供了一个Promise数组,n
并为您提供了一系列已n
解析的值。下面我们显示链接和并行:
// an async function to simulate loading an item from some server function loadItem(id: number): Promise<{ id: number }> { return new Promise((resolve) => { console.log('loading item', id); setTimeout(() => { // simulate a server delay resolve({ id: id }); }, 1000); }); } // Chained / Sequential let item1, item2; loadItem(1) .then((res) => { item1 = res; return loadItem(2); }) .then((res) => { item2 = res; console.log('done'); }); // overall time will be around 2s // Concurrent / Parallel Promise.all([loadItem(1), loadItem(2)]) .then((res) => { [item1, item2] = res; console.log('done'); }); // overall time will be around 1s
有时,您想运行一系列异步任务,但是只要解决了其中任何一个任务,您就可以获得所需的一切。为这种情况Promise
提供静态Promise.race
功能:
Sometimes, you want to run a series of async tasks, but you get all you need as long as any one of these tasks is settled. Promise
provides a static Promise.race
function for this scenario:
var task1 = new Promise(function(resolve, reject) { setTimeout(resolve, 1000, 'one'); }); var task2 = new Promise(function(resolve, reject) { setTimeout(resolve, 2000, 'two'); }); Promise.race([task1, task2]).then(function(value) { console.log(value); // "one" // Both resolve, but task1 resolves faster });
翻译来源:https://gitee.com/yunwisdoms/typescript-book/blob/master/docs/promise.md
更多推荐
所有评论(0)