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
|
请发表评论