• 设为首页
  • 点击收藏
  • 手机版
    手机扫一扫访问
    迪恩网络手机版
  • 关注官方公众号
    微信扫一扫关注
    公众号

TypeScript入门教程 之 Promise

原作者: [db:作者] 来自: [db:来源] 收藏 邀请

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结果。使用基于回调的异步函数时,需要记住以下几点:

  1. 永远不要调用回调两次。
  2. 永远不要抛出错误。

但是,此简单功能无法容纳第二点。实际上,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的更好方法。

 

承诺

许诺可以是pendingfulfilledrejected

让我们来看看创造一个诺言。这是调用的一个简单的事情newPromise(许诺构造函数)。该承诺的构造函数传递resolvereject功能稳定的承诺状态:

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人参与评论

请发表评论

全部评论

专题导读
上一篇:
TypeScript 类型发布时间:2022-07-18
下一篇:
Typescript实战---(10)命名空间和模块发布时间:2022-07-18
热门推荐
热门话题
阅读排行榜

扫描微信二维码

查看手机版网站

随时了解更新最新资讯

139-2527-9053

在线客服(服务时间 9:00~18:00)

在线QQ客服
地址:深圳市南山区西丽大学城创智工业园
电邮:jeky_zhao#qq.com
移动电话:139-2527-9053

Powered by 互联科技 X3.4© 2001-2213 极客世界.|Sitemap