JavaScript Promise

发布于 2022-02-02 23:17:30 字数 8717 浏览 921 评论 0

在 Promise 出现之前,如果大家需要实现异步操作,通用的做法是事件加上回调函数,如果我们有多个异步操作需要嵌套执行的话,那么代码将变得非常难于阅读。让我们来看一个具体的代码例子。在下面的例子中,我们首先通过 Http request 调用一个 web service,然后将从 web service 收到的数据写入一个本地文件。从任务的角度来看,这是一个非常简单的任务,但是当你第一次看到这个代码的时候,一定觉得头很晕,因为在这段代码中

  • 对 web service 进行调用的代码和写文件的代码混杂在一起(写文件的代码嵌套在 end 事件的回调函数中),造成代码模块不清晰,阅读和理解起来比较费劲。
  • 对错误的处理分散在代码的各个地方,而且错误处理的实现方式都不一样。对 web service 进行调用的代码,通过监听 error 事件来处理错误,并且将错误输出到控制套;而写文件的代码,是通过回调函数来处理错误,并将错误通过 throw 语句抛出。
var http = require("http");
var fs = require("fs");
var querystring = require("querystring");
var postData = querystring.stringify({
  'msg' : 'Hello World!'
});

var options = {
  hostname: '127.0.0.1',
  port: 8080,
  path: '/upload',
  method: 'POST',
  headers: {
    'Content-Type': 'application/x-www-form-urlencoded',
    'Content-Length': postData.length
  }
};

var req = http.request(options, (res) => {
  console.log(`STATUS: ${res.statusCode}`);
  console.log(`HEADERS: ${JSON.stringify(res.headers)}`);
  res.setEncoding('utf8');

  var dataReceived = ""

  res.on('data', (chunk) => {
    dataReceived = dataReceived + chunk.toString();
    console.log(`BODY: ${chunk}`);
  });

  res.on('end', () => {
    console.log('No more data in response.')
    //now we try to write the message to a file
    fs.writeFile("temp.txt", dataReceived, function(err){
        if (err){
            throw err;
        }
        console.log("Write file temp.txt succ");
    });
  })
});

req.on('error', (e) => {
  console.log(`problem with request: ${e.message}`);
});

// write data to request body
req.write(postData);
req.end();

接下来,让我们使用 Promise 重写上面的代码。在重写的代码中,我们可以看到:

  • 对 web service 进行调用的代码和写文件的代码完全分离开了,代码结构变得非常清晰。在阅读代码的过程中,不会再被不相关的代码所干扰。
  • Promise 对象对外提供了统一的回调函数接口(resolve 和 reject 回调函数 ),在重写的代码中,我们可以很容易的把对web service的请求分装到一个模块中,从而对外隐藏Http Request的所有细节。
  • 通过 Promise 对象的封装,对错误代码的处理被统一了,都是通过对 reject 函数调用来说明异步操作过程中有错误发生,而且错误被集中到 .catch 代码段进行了处理(错误都被输出到了控制台)。
  • 通过 Promise 的封装,异步操作的代码变得和同步操作代码很像,更方便其他人理解代码的处理逻辑。
'use strict';
var http = require("http");
var fs = require("fs");
var querystring = require("querystring");
var postData = querystring.stringify({
  'msg' : 'Hello World!'
});

var options = {
  hostname: '127.0.0.1',
  port: 8080,
  path: '/upload',
  method: 'POST',
  headers: {
    'Content-Type': 'application/x-www-form-urlencoded',
    'Content-Length': postData.length
  }
};

var pHttpRequest = new Promise(function(resolve, reject){
    let req = http.request(options, (res) => {
      console.log(`STATUS: ${res.statusCode}`);
      console.log(`HEADERS: ${JSON.stringify(res.headers)}`);
      res.setEncoding('utf8');

      let dataReceived = ""

      res.on('data', (chunk) => {
        dataReceived = dataReceived + chunk.toString();
      });

      res.on('end', () => {
        resolve(dataReceived);
      })
    });

    req.on('error', function(e){
        reject(e);
    });

    req.write(postData);
    req.end();    
})

pHttpRequest.then(
    //http request promise成功时候的处理
    //在http request promise成功的时候,开始处理写文件操作
    function(dataReceived){
        return new Promise(function(resolve, reject){
            fs.writeFile("temp.txt", dataReceived, function(e){
                if (e) reject(e);
                else resolve("Write file succ");
            });
        })
    }
).then(
    //writeFile promise成功时候的处理
    function(msg){
        console.log(msg);
    }
).catch(
    //全局错误处理
    function(err){
       console.log("some error happen(" + err + ")");
    }
)

正式基于 Promise 对象有上面所说的这些优点,ES6 正式将 Promise 对象编程了系统的一个内置对象,大家再也不用通过第三方库开始用 Promise 对象了。

Promise 对象的特性

  • Promise 对象一旦创建,就开始执行了,你没有办法取消 Promise 对象的执行。
  • Promise 对象的状态只由异步操作的结果决定,没有任何其他的操作可以改变Promise对象的状态如果异步操作执行成功,Promise 对象将进入 Resolved 状态,同时resolve函数将被调用;如果异步操作执行失败,Promise 对象将进入 Rejected 状态,同时 reject 函数将被调用。
  • Promise对象一旦进入 Resolved 或者 Rejected 状态,状态将不可能再发生变化,在 Promise 对象被销毁之前,将一直保持 Resolved 或者 Rejected 状态。
  • 因为Promise对象的实现方法,在异步操作过程中出现的异常是不会被抛出的,因此需要在 Promise 对象内部进行处理(通过提供 .catch 代码段来实现)。

下图表示了一个 Promise 对象的整个生命周期。

Promise 对象的使用

创建 Promise 对象

在 ES6 中,你可以通过 new Promise(function(resolve, reject){
})
来创建 Promise 对象,下面是一个具体的代码。

'use strict';
var fs = require("fs");
var promiseObj = new Promise(function(resolve, reject){
    //put some code to call
    fs.readFile("temp.txt", (err, data) => {
        if (err){
            reject(err);
        }else{
            resolved(data);
        }
    });
});

关联 resolve function 和 reject function

在上面的例子中,如果你执行这个代码,你会发现没有任何的效果(没有输出),那是因为你没有给这个创建的 Promise 对象关联相应的 resolve 和 reject 函数。下面是一个进一步的例子,这个例子将给 Promise 对象绑定 resolve 和 reject 函数,你就可以看到效果了。在我的测试环境中,因为我们有名为 temp.txt 的文件存在,所以输出了 file read fail

'use strict';
var fs = require("fs");
var promiseObj = new Promise(function(resolve, reject){
    //put some code to call
    fs.readFile("temp.txt", (err, data) => {
        if (err){
            reject(err);
        }else{
            resolved(data);
        }
    });
});
promiseObj.then(function(data){
    console.log("file read succ");
}, function(err){
    console.log("file read fail");
})

在通常情况下,reject 状态的函数我们一般不在 then 中设置,而是在 catch 中设置,这样代码看起来更像是传统意义上的同步代码(和try...catch 比较)。因此上面的例子可以重新写成

'use strict';
var fs = require("fs");
var promiseObj = new Promise(function(resolve, reject){
    //put some code to call
    fs.readFile("temp.txt", (err, data) => {
        if (err){
            reject(err);
        }else{
            resolved(data);
        }
    });
});
promiseObj.then(function(data){
    console.log("file read succ");
}).catch(function(err){
    console.log("file read fail");
});

级联多个 Promise 对象

Promise 对象的 resolve 函数的参数可以是另外一个 Promise 对象,这样就可以将 2 个 Promise 对象级联起来。下面是一个简单的例子

'use strict';
var p1 = new Promise(function(resolve, reject){
    setTimeout(() => reject(new Error("something test"), 3000));
});

var p2 = new Promise(function(resolve, reject){
    setTimeout(() => resolve(p1), 1000);
});

p2.then(function(data){
   console.log("p2 succ");
}).catch(function(err){
   console.log("p2 fail");
});

Promise 的特殊函数

Promise.all()

将多个 Promise 包装成一个全新的 Promise Object,如果所有的 Promise 被 Resolved,那么新的 Promise 将被 Resolve;否则新的 Promise 将被 Reject。

Promise.race()

和 .all 一样,.race 将把多个 Promise 包装成一个新的 Promise Object,不同的地方是。这些 Promise 之中任何一个 Resolve 或者 Reject 了,新的 Promise 就被 Resolve 或者 Reject 了。

Promise.resolve()

将传入的对象封装成一个 Promise 对象返回。resolve 方法根据以下的规则返回 Promise 对象。

  • 如果输入的参数本身是一个 Promise 对象,那么 resolve 方法直接返回这个对象;
  • 如果输入的对象本身有 then 方法(必须是一个可以接受2个 function 的方法),那么 resolve 将这个对象转换成 Promise 对象,并理解调用 then 方法;下面是一个例子
//for this example, you will see following output
//   then function in thenobject
//   Promise object resolve function is called Then function is called
var thenobject = {
    then: function(resolve, reject){
       console.log("then function in thenobject");
       resolve("Then function is called");
    }
};

var pObj = Promise.resolve(thenobject);
pObj.then(function(data){
    console.log("Promise object resolve function is called " + data);
});
  • 如果传入的参数就是一个普通对象,那么返回的 Promise 对象直接处于 resolved 状态,并且输入的参数将作为 resolved 状态下调用的函数的参数。下面是一个具体的例子。
//for this example, you will see following output
//   Promise object resolve function is called Hello World!
var pObj = Promise.resolve("Hello World!");
pObj.then(function(data){
    console.log("Promise object resolve function is called " + data);
});

如果没有输入参数,那么返回的Promise对象直接处于 resolved 状态,并且 resolved 状态下调用的函数没有输入参数。

done() method

通过在Promise的调用链最后使用这个方法,可以保证catch到任何的错误。

finally() method

如果你需要在 Promise 结束的时候(不管 resolve 还是 reject 结束),都有一个函数被调用,那么就需要使用这个方法。这个方法接受一个回调函数作为输入。当 Promise 结束的时候,这个回调函数将被调用。

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据

关于作者

JSmiles

生命进入颠沛而奔忙的本质状态,并将以不断告别和相遇的陈旧方式继续下去。

0 文章
0 评论
84960 人气
更多

推荐作者

烙印

文章 0 评论 0

singlesman

文章 0 评论 0

独孤求败

文章 0 评论 0

晨钟暮鼓

文章 0 评论 0

    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文