Javascripts中的promise

一、基本概念

1. 异步

所谓 " 异步 “,简单说就是一个任务分成两段, 先执行第一段, 然后转而执行其他任务去, 等做好了准备, 再回过头执行第二段。

比如, 有一个任务是读取文件进行处理, 任务的第一段是向操作系统发出请求, 要求读取文件。 然后, 程序执行其他任务, 等到操作系统返回文件,再接着执行任务的第二段( 处理文件)。 这种不连续的执行, 就叫做异步。

相应地, 连续的执行就叫做同步。 由于是连续执行, 不能插入其他任务, 所以操作系统从硬盘读取文件的这段时间, 程序只能干等着什么也做不了。

2. 回调函数

javascript 语言对异步编程的实现, 就是回调函数。 所谓回调函数, 就是把任务的第二段单独写在一个函数里面, 等到准备好了,继续执行的时候, 就直接调用这个函数。 它的英语名字 callback, 直译过来就是 " 回调 “。

读取文件进行处理, 是这样的,readfile 是异步的,readfileSync 是同步的。

1fs.readfile('/etc/passwd', function(err, data) {
2  if(err) throw err;
3  console.log(data);
4});

上面代码中, readfile 函数的第二个参数, 就是回调函数, 也就是任务的第二段。 等到操作系统返回了 / etc / passwd这个文件以后, 回调函数才会执行。

==一个有趣的问题是, 为什么 node.js 约定, 回调函数的第一个参数, 必须是错误对象 err( 如果没有错误, 该参数就是 null)? 原因是执行分成两段, 在这两段之间抛出的错误, 程序无法捕捉, 只能当作参数, 传入第二段。==

3. promise

“想像一下你是个小孩,你妈妈 promise 承诺你下星期给你一部新手机”

只有下周来临的时候,你才会知道你真的得到一部手机,或者是骗你玩的。

这就是 promise. 一个 promise 有三种状态:

  1. Pending: 待定,你不知道你是否能得到一部手机
  2. Resolved: 妈妈很高兴,你得到一部手机
  3. Rejected: 妈妈不高兴,你没有得到一部手机

状态图如下:

image-20220527222312367

语法很简单:

1// promise syntax look like this
2new Promise(function (resolve, reject) { ... } );

再看一下上面问题的解决方法:

首先确定 Mom 的状态是 unhappy,然后建立一个 Promise 来确定是否得到手机,最后用一个函数 askMom 来调用这个 Promise

resolve() 中可以放置一个参数用于向下一个 then 传递一个值,then 中的函数也可以返回一个值传递给 then。但是,如果 then 中返回的是一个 Promise 对象,那么下一个 then 将相当于对这个返回的 Promise 进行操作。

reject() 参数中一般会传递一个异常给之后的 catch 函数用于处理异常。

但是请注意以下两点:

  • resolve 和 reject 的作用域只有起始函数,不包括 then 以及其他序列;
  • resolve 和 reject 并不能够使起始函数停止运行,别忘了 return。
 1var isMomHappy = false;
 2
 3// Promise
 4var willIGetNewPhone = new Promise(
 5    function (resolve, reject) {
 6        if (isMomHappy) {
 7            var phone = {
 8                brand: 'Samsung',
 9                color: 'black'
10            };
11            resolve(phone); // resolved
12        } else {
13            var reason = new Error('mom is not happy');
14            reject(reason); // rejected
15        }
16
17    }
18);
19
20var askMom = function () {
21    willIGetNewPhone
22        .then(function (fulfilled) {
23            // yay, you got a new phone
24            console.log(fulfilled);
25             // output: { brand: 'Samsung', color: 'black' }
26        })
27        .catch(function (error) {
28            // oops, mom didn't buy it
29            console.log(error.message);
30             // output: 'mom is not happy'
31        });
32};
33
34askMom();

很有点意思吧。抽象了,那就实际点。

javascripts 中的 fetch 函数就是基于Promise范式的,promise 的 resolves 绑在了 Response 上。Promise 类有 .then() .catch() 和 .finally() 三个方法,这三个方法的参数都是一个函数,.then() 可以将参数中的函数添加到当前 Promise 的正常执行序列,.catch() 则是设定 Promise 的异常处理序列,.finally() 是在 Promise 执行的最后一定会执行的序列。 .then() 传入的函数会按顺序依次执行,有任何异常都会直接跳到 catch 序列:

 1    fetch("http://192.168.1.6/graph_image_hubot.php?action=view&local_graph_id=13619&rra_id=5")
 2    .then(response =>  {
 3      if (response.ok) {
 4         return response.buffer();
 5      }
 6      throw new Error('Network response was not ok.');
 7    })
 8    .then(buffer => {
 9      const formData = new FormData();
10      formData.append( 'media', buffer, {filename: 'bandwidth.png', contentType: 'image/png' } );
11      robot.wwork.sendImageMessage(owner, formData);
12    });

上面的程序实际上是从 cacti 服务器拿到一张流量图片,然后 fetch 的结果 anyway,response总是有东西的,都会是成功的,所以我们必须再用 response.ok 来判断,成功就继续得到 buffer 流,最终送到 hubot 中去。三个过程用 then 串了起来,每一步都成功才会进行下一步。

这样写避免了回调地狱,看起来也比较舒服。

那什么时候用 promise 呢,它是异步的,有大IO读写硬盘、读写网络的时候用,上面是读写网络。下面再来一个读写磁盘的:

1const {promise: {readFile, writeFile}} = require('fs');
2(async () => {
3    let content = await readFile('./data.txt', 'utf8');
4    await writeFile('2.txt', content, 'utf8');
5    console.log('ok');
6})();

上面的语法是 ES7 的,它更尽了一步,首先声明一个匿名的函数是 async 异步的,然后用await来进行等待,将读和写两个大操作都放到 await 的一步操作中去,这样程序看起来就变成同步的一步步等待了。跟 ES6 的 promise 比起来,更进了一步。


Javascripts中的map
Shell进阶技巧
comments powered by Disqus