Node.js中callback/async await/Promise/promisify

一、回调函数(callback)

1、概念

A callback is a function that is passed as an argument to another function and is executed after its parent function has completed.
翻译:回调函数是作为参数传递给另一个函数的函数,在父函数完成后执行。

2、例子说明

var fs = require("fs");
var a
 
function f(x) {
    console.log(x)
}
 
function writeFile() {
    fs.writeFile('input01.txt', 'www.baidu.com', function (err) {
        if (!err) {
            console.log("文件写入完毕!")
            a = 1
        }
    });
}
 
a = 0
writeFile()
f(a)

输出结果:
0
文件写入完毕!

代码说明:设置一个全局变量 a = 0,后执行writeFile函数(也就是写入一个文件input01.txt),这个函数里面有一行c = 1,函数执行完毕之后再跳出来调用f()函数,f()函数,就是把打印一个变量。

按照 “正常” 逻辑,首先 a=0,然后调用writeFile函数,该函数里面有一句a = 1,最后再调用f(a),又因为调用writeFile()是在f(a)之前,所以 a=1 这条语句肯定是会被执行到,那么结果应该是打印1,结果竟然是0,明明我们在writeFile函数里我们重新对 a 进行了赋值,为什么结果还是0呢?

因为程序运行到writeFile()这一行的时候,是一个比较耗时的IO操作,JS碰到这种操作并不会停在原地一直等待直到函数执行完毕,而是直接运行下一条代码(即f(a)),而此时 a = 1这一行代码其实并没有被执行到,所以打印出来的结果还是0 !

如果要打印出 a= 1,只需要将f(a)也放进writeFile()函数里面,就能保证a = 1之后再调用f(a):

var fs = require("fs");
var a
 
function f(x) {
    console.log(x)
}
 
function writeFile() { 
    fs.writeFile('input01.txt', 'www.baidu.com', function (err) {
        if (!err) {
            console.log("文件写入完毕!")
            a = 1
            f(a)
        }
    });
}
 
a = 0;
writeFile();

输出结果:
文件写入完毕!
1

代码说明: 先执行writeFile()函数,再执行writeFile中的f(a),a = 1 一定会被执行到。

但是改成这样并不完美,因为这么做就相当于将f()"焊死"在writeFile()里了,如果此处我最终想调用的函数不是f()而是别的其他函数f1(),f2(),f3()…应该怎么写?难不成要写几个不同的writeFile(),而他们之间的区别仅仅是最后调用的那个函数不同?显然是不可取得,而使用回调函数就可解决:“关键字” callback 。(准确地说callback并不真的是Javascript里的关键字,但是鉴于大家都约定成俗把callback这个单词作为回调函数的默认选择了,这里姑且就不严谨地称它为"关键字"吧)

var fs = require("fs");
 
function f(x) {
    console.log(x)
}
 
function writeFile(callback) { //callback,表示这个参数不是一个普通变量,而是一个函数
    fs.writeFile('input01.txt', 'www.baidu.com', function (err) {
        if (!err) {
            console.log("文件写入完毕!")
            a = 1
            callback(a) // 因为我们传进来的函数名是f(),所以此行相当于调用一次f(c)
        }
    });
}
var a = 0
writeFile(f) // 函数f作为一个参数传进writeFile函数

输出结果:
文件写入完毕!
1

代码说明:出现了两次callback,第一个callback出现在 writeFile 的形参里,起定义的作用,表示这个参数并不是一个普通变量,而是一个函数,也就是前面所说的重点,即所谓的“以函数为参数”。 第二个callback出现在a = 1下面,表示此处“执行”从形参传递进来的那个函数。

这样一来,writeFile()函数在执行完毕之后到底调用哪个函数就变“活”了,如果我们想writeFile()函数执行完之后并不是像第二个例子那样只能调用f(),而是还有别的函数比如说x() y() z(),那么只需要写成 writeFile(x),writeFile(y)… 就行了。

总结: 在大多数编程语言中,函数的形参总是从外向内传递参数,但在JS中,如果形参碰到“关键字” callback 则完全相反,它表示从内向外反向调用某个外部函数。

此处并不一定非要写为“callback”,你可以任意写成abc, foo...等等。callback只是一种约定俗成的写法,它明确地告诉代码阅读者:此处是一个回调函数。

有时候,我们会看到一些函数的形参列表里直接嵌套一个函数的情况,其本质上仍然是回调函数,因为没有了函数名,所以也称匿名函数。

var fs = require("fs");
 
function writeFile(callback) { 
    fs.writeFile('input01.txt', 'www.baidu.com', function (err) {
        if (!err) {
            console.log("文件写入完毕!")
            a = 1
            callback(a) 
        }
    });
    
}
var a = 0
writeFile(function (x) {
    console.log(x)
});
输出结果:
文件写入完毕!
1

writeFile()函数不变,只是在调用它的时候,直接将函数体嵌在参数列表里了,其作用跟上一个例子完全一样。其实在本例中,fs.writeFile函数后面也有一个匿名回调函数 function (err) {},这个函数表示当文件写入完毕后,就回调它,如果在写入过程中出现了错误,则通过变量err携带出来。

说明: 在JS里,并非所有操作都是异步的,比如for循环,无论这个for循环需要耗时多长,系统也一定会等它转完之后才会执行下面的语句。

二、Promise介绍

1、回调地狱

首先我们来看什么是回调地狱

在使用JavaScript时,为了实现某些逻辑经常会写出层层嵌套的回调函数,如果嵌套过多,会极大影响代码可读性和逻辑,这种情况也被成为回调地狱。

function pick(next){
    setTimeout(()=>{
        console.log('收到信息',new Date());
        next();
    },500);
}
function groundMouth(next){
    setTimeout(()=>{
        console.log('货物装载',new Date());
        next();
    },400);
}
function blow(next){
    setTimeout(()=>{
        console.log('打包出仓',new Date());
        next();
    },300);
}
function PEbag(next){
    setTimeout(()=>{
        console.log('集成装车',new Date());
        next();
    },200);
}
function pack(){
    setTimeout(()=>{
        console.log('发送运输',new Date());
    },100);
}

pick(()=>{
    groundMouth(()=>{
        blow(()=>{
            PEbag(()=>{
                pack();
            });
        });
    });
});

输出结果:
收到信息 2022-03-20T14:24:15.363Z
货物装载 2022-03-20T14:24:15.771Z
打包出仓 2022-03-20T14:24:16.072Z
集成装车 2022-03-20T14:24:16.273Z
发送运输 2022-03-20T14:24:16.375Z

如果还有后续步骤,需要一直嵌套下去,这样代码的可读性就变差了。

如果使用Promise该怎么解决呢?

2、Promise介绍

2.1 Promise的状态

用 new Promise 实例化的promise对象有以下三个状态。

  • “has-resolution” 即Fulfilled resolve(成功) 时。此时会调用 onFulfilled
  • “has-rejection” 即Rejected reject(失败) 时。此时会调用 onRejected
  • “unresolved” 即Pending 既不是resolve也不是reject的状态。也就是promise对象刚被创建后的初始化状态等

promise对象的状态,从Pending转换为Fulfilled或Rejected之后, 这个promise对象的状态就不会再发生任何变化。也就是说,只有异步操作的结果可以决定当前是哪一种状态,其他任何操作都无法改变这种状态;一旦状态改变,就不会再改变。

Promise与Event等不同,在 .then 后执行的函数可以肯定地说只会被调用一次。

Promise中的函数是同步的,只有.then后的函数才是异步的

image.png

2.2 构造器

创建一个Promise对象,可以使用new调用Promise的构造器来进行实例化。

var promise = new Promise(function(resolve, reject) { // 异步处理
// 处理结束后、调用resolve 或 reject
});

2.3 实例方法

  • Promise.prototype.then

对通过new生成的promise对象为了设置其值在 resolve (成功)/ reject(失败)时调用的回调函数 可以使用 promise.then()实例方法(也就是说作用是为 Promise 实例添加状态改变时的回调函数。)。

promise.then(onFulfilled, onRejected)

then方法的第一个参数是 Resolved 状态的回调函数, 第二个参数( 可选) 是 Rejected 状态的回调函数。

  • resolve(成功)时 onFulfilled 会被调用
  • reject(失败)时 onRejected 会被调用

onFulfilled 、 onRejected 两个都为可选参数。

then方法返回的是一个新的 Promise 实例( 注意,不是原来那个 Promise 实例)。 因此可以采用链式写法, 即then方法后面再调用另一个`then方法。


getJSON("/post/1.json") //返回一个Promise对象
.then(function(post) {  
    return getJSON(post.commentURL);  //返回一个Promise对象
})
.then(function funcA(comments) {  
    console.log("Resolved: ", comments);  
}, function funcB(err) {  
    console.log("Rejected: ", err);  
});

上面的代码使用then方法,依次指定了两个回调函数。 第一个回调函数完成以后,会将返回结果作为参数,传入第二个回调函数。采用链式的then,可以指定一组按照次序调用的回调函数。

  • Promise.prototype.catch()

promise.then成功和失败时都可以使用。另外在只想对异常进行处理时可以采用Promise.then(undefined, onRejected)这种方式,只指定reject时的回调函数即可。

Promise.prototype.catch方法是.then(null, rejection) 的别名, 用于指定发生错误时的回调函数,等同于抛出错误。
上文的代码可以改造成如下:

getJSON("/post/1.json") //返回一个Promise对象
.then(function(post) {  
    return getJSON(post.commentURL);  //返回一个Promise对象
})
.then(function (comments) {  
    console.log("Resolved: ", comments);  
})
.catch(err) {  
    console.log("Rejected: ", err);  
});  

需要注意的是,如果 Promise 状态已经变成Resolved, 再抛出错误是无效的。

var promise = new Promise(function(resolve, reject) {  
  resolve('success');  
  throw new Error('error');  
});  
promise  
  .then(function(value) {  
      console.log(value)  
  })  
  .catch(function(error) {  
      console.log(error)  
  });

输出结果:
success

上面代码中, Promise 在resolve语句后面,再抛出错误,不会被捕获, 等于没有抛出。

Promise 对象的错误具有“ 冒泡” 性质, 会一直向后传递, 直到被捕获为止。 也就是说, 错误总是会被下一个catch语句捕获。

var catchError = new Promise(function(resolve, reject) {  
  setTimeout(function(){
      resolve('success')
  }, 1000)  
}) 

catchError
.then(function(value){
  console.log('a')
})
.then(function(value){
  throw new Error('test');  
  console.log('b')
})
.then(function(value){
  console.log('c')
})
.catch(function(error){
  console.log(error)
})
输出结果:
a
Error: test
    at D:\upyun\test\promise\promise12.js:26:9

上面代码中,一共有四个Promise 对象:一个由’catchError’产生, 三个由then产生。它们之中的第二个then方法出了错误,中断了下面的then方法,直接被最后一个catch捕获。

建议总是使用catch方法, 而不使用then方法的第二个处理错误的参数。

跟传统的try / catch代码块不同的是,如果没有使用catch方法指定错误处理的回调函数,Promise 对象抛出的错误不会传递到外层代码, 即不会有任何反应。

var foo = function() {  
  return new Promise(function(resolve, reject) {  
      //  下面一行会报错,因为 a 没有声明  
      resolve(a + 2);  
  });  
};  
foo().then(function() {  
  console.log('everything is great');  
});
输出结果:
ReferenceError: a is not defined

上面代码中,foo函数产生的 Promise 对象会报错, 但是由于没有指定catch方法,这个错误不会被捕获,也不会传递到外层代码, 导致运行后没有任何输出。

注意, Chrome 浏览器不遵守这条规定, 它会抛出错误“ ReferenceError: x is not defined”。

var promise = new Promise(function(resolve, reject) {  
  resolve("success");  
  setTimeout(function() {  
      throw new Error('error')  
  }, 0)  
});  
promise.then(function(value) {  
  console.log(value)  
}); 
输出结果:
success
D:\upyun\test\promise\promise12.js:49
      throw new Error('error')
      ^

Error: error

上面代码中,Promise指定在下一轮“ 事件循环” 再抛出错误, 结果由于没有指定使用try...catch语句,就冒泡到最外层,成了未捕获的错误。 因为此时,Promise 的函数体已经运行结束了, 所以这个错误是在Promise函数体外抛出的。

Node.js 有一个unhandledRejection事件,专门监听未捕获的reject错误。unhandledRejection事件的监听函数有两个参数, 第一个是错误对象, 第二个是报错的 Promise 实例, 它可以用来了解发生错误的环境信息。


process.on('unhandledRejection', function(err, p) {  
    console.error(err.stack)  
}); 

需要注意的是,catch方法返回的还是一个Promise对象,因此后面还可以接着调用then方法。

var foo = function() {  
  return new Promise(function(resolve, reject) {  
      //  下面一行会报错,因为 a 没有声明  
      resolve(a + 2);  
  });  
};  
foo()  
  .catch(function(error) {  
      console.log('Error', error);  
  })  
  .then(function() {  
      console.log('success');  
  });
输出结果:
Error ReferenceError: a is not defined
success

上面代码运行完catch方法指定的回调函数,会接着运行后面那个then方法指定的回调函数。 如果没有报错, 则会跳过catch方法。

2.4 静态方法

Promise 这样的全局对象还拥有一些静态方法。

包括 Promise.all() 还有 Promise.resolve() 等在内,主要都是一些对Promise进行操作的 辅助方法。

Promise.all() 方法接收一个promise的iterable类型(注:Array,Map,Set都属于ES6的iterable类型)的输入,并且只返回一个Promise实例, 那个输入的所有promise的resolve回调的结果是一个数组。这个Promise的resolve回调执行是在所有输入的promise的resolve回调都结束,或者输入的iterable里没有promise了的时候。它的reject回调执行是,只要任何一个输入的promise的reject回调执行或者输入不合法的promise就会立即抛出错误,并且reject的是第一个抛出的错误信息。

const promise1 = Promise.resolve(3);
const promise2 = 42;
const promise3 = new Promise((resolve, reject) => {
  setTimeout(resolve, 100, 'foo');
});

Promise.all([promise1, promise2, promise3]).then((values) => {
  console.log(values);
});
输出结果:
[ 3, 42, 'foo' ]

语法

Promise.all(iterable);

参数

返回值

  • 如果传入的参数是一个空的可迭代对象,则返回一个已完成(already resolved) 状态的 Promise
  • 如果传入的参数不包含任何 promise,则返回一个异步完成(asynchronously resolved)  Promise。注意:Google Chrome 58 在这种情况下返回一个已完成(already resolved) 状态的 Promise
  • 其它情况下返回一个处理中(pending)Promise。这个返回的 promise 之后会在所有的 promise 都完成或有一个 promise 失败时异步地变为完成或失败。返回值将会按照参数内的 promise 顺序排列,而不是由调用 promise 的完成顺序决定。

说明

此方法在集合多个 promise 的返回结果时很有用。

完成(Fulfillment): 如果传入的可迭代对象为空,Promise.all 会同步地返回一个已完成(resolved)状态的promise
如果所有传入的 promise 都变为完成状态,或者传入的可迭代对象内没有 promisePromise.all 返回的 promise 异步地变为完成。

在任何情况下,Promise.all 返回的 promise 的完成状态的结果都是一个数组,它包含所有的传入迭代参数对象的值(也包括非 promise 值)。

失败/拒绝(Rejection): 如果传入的 promise 中有一个失败(rejected),Promise.all 异步地将失败的那个结果给失败状态的回调函数,而不管其它 promise 是否完成。

-Promise.resolve()

Promise.resolve(value)方法返回一个以给定值解析后的Promise 对象。如果这个值是一个 promise ,那么将返回这个 promise ;如果这个值是thenable(即带有"then"方法),返回的promise会“跟随”这个thenable的对象,采用它的最终状态;否则返回的promise将以此值完成。此函数将类promise对象的多层嵌套展平。

语法

Promise.resolve(value);

参数

value

将被Promise对象解析的参数,也可以是一个Promise对象,或者是一个thenable。

返回值

返回一个带着给定值解析过的Promise对象,如果参数本身就是一个Promise对象,则直接返回这个Promise对象。

描述

静态方法 Promise.resolve返回一个解析过的Promise对象。

1. 使用静态`Promise.resolve`方法
Promise.resolve("Success").then(function(value) {
  console.log(value); 
}, function(value) {
  // 不会被调用
});
输出结果:
Success

2.resolve一个数组
var p = Promise.resolve([1,2,3]);
p.then(function(v) {
  console.log(v[0]); // 1
});

2.5 还有需要注意,Promise创建后回立刻执行

var promise = new Promise(function(resolve, reject) {  
  console.log('Promise');  
  resolve();  
});  
promise.then(function() {  
  console.log('Resolved.');  
});  
console.log('hello world'); 
输出结果:
Promise
hello world
Resolved.

上面代码中, Promise 新建后立即执行, 所以首先输出的是“ Promise”。 然后, then方法指定的回调函数, 将在当前脚本所有同步任务执行完才会执行, 所以“ Resolved” 最后输出。

2.6 Promise也是有缺点的

  • 无法取消Promise,一旦新建它就会立即执行,无法中途取消。
  • 如果不设置回调函数, Promise内部抛出的错误,不会反应到外部。
  • 当处于Pending状态时, 无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。

2.7 创建Promise对象流程

  • new Promise(fn)返回一个Promise对象

  • fn中指定异步等处理逻辑

    • 处理结果正常的话,调用 resolve(处理结果值)
    • 处理结果错误的话,调用 reject(Error对象)

2.8 创建XHR的promise对象

首先,创建一个用Promise把XHR处理包装起来的名为 getURL 的函数。

function getURL(URL) {
  return new Promise(function (resolve, reject) {
      var req = new XMLHttpRequest(); 

      req.open('GET', URL, true); 

      req.onload = function () {
          if (req.status === 200) { 
              resolve(req.responseText);
          } else {
              reject(new Error(req.statusText));
          } 
      };
      
      req.onerror = function () { 
          reject(new Error(req.statusText));
      };
      req.send(); 
  });
}
// 运行示例
var URL = "http://httpbin.org/get"; 
getURL(URL).then(function onFulfilled(value){
  console.log(value); 
}).catch(function onRejected(error){
  console.error(error); 
});

getURL 只有在通过XHR取得结果状态为200时才会调用 resolve,而其他情况(取得失败)时则会调用 reject 方法。

resolve(req.responseText)

resolve函数的作用是, 将 Promise对象的状态从“ 未完成” 变为“ 成功”( 即从 Pending变为Resolved),在异步操作成功时调用,并将异步操作结果,作为参数传递出去。

参数并没有特别的规则,基本上把要传给回调函数参数放进去就可以了。 ( then 方法可以接收到这个参数值)

reject(new Error(req.statusText))

reject函数的作用是,将 Promise 对象的状态从“ 未完成” 变为“ 失败”( 即从 Pending 变为Rejected),在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。

上文中,XHR中 onerror事件被触发的时候就是发生错误时,所以理所当然调用 reject 。发生错误时,创建一个Error对象后再将具体的值传进去。传给 的参数也没有什么特殊的限制,一般只要是Error对象(或者 继承自Error对象)就可以。

2.9 解决上面的回调地狱

function pick(){
  return new Promise((resolve, reject) => {
    setTimeout(()=>{
      console.log('收到信息',new Date());
      resolve();
  },500);
  })

}
function groundMouth(){
  return new Promise((resolve, reject) => {
    setTimeout(()=>{
      console.log('货物装载',new Date());
      resolve();
  },400);
  })
}
function blow(){
  return new Promise((resolve, reject) => {
    setTimeout(()=>{
      console.log('打包出仓',new Date());
      resolve();
  },300);
  })

}
function PEbag(){
  return new Promise((resolve, reject) => {
    setTimeout(()=>{
      console.log('集成装车',new Date());
      resolve();
  },200);
  })
}
function pack(){
  return new Promise((resolve, reject) => {
    setTimeout(()=>{
      console.log('发送运输',new Date());
      resolve();
  },100);
  })
}
pick()
  .then(groundMouth)
  .then(blow)
  .then(PEbag)
  .then(pack)

// pick(()=>{
//   groundMouth(()=>{
//       blow(()=>{
//           PEbag(()=>{
//               pack();
//           });
//       });
//   });
// });
输出结果:
收到信息 2022-03-21T03:24:56.520Z
货物装载 2022-03-21T03:24:56.925Z
打包出仓 2022-03-21T03:24:57.235Z
集成装车 2022-03-21T03:24:57.436Z
发送运输 2022-03-21T03:24:57.544Z

三、async await

首先说明一下异步,以及常见的微任务和宏任务

1、异步,微任务,宏任务

异步

  • 异步是用来解决JS单线程等待这种问题的
  • 异步是基于回调函数的形式来实现的
  • 常见的异步有:setTimeout、ajax、Promise……then、async/await、图片加载、网络请求资源
  • 牢记5个版块 Call Stack、 Web APIs、 Browser console、 Callback Queue 、 micro task queue 这五个版块透露出异步的执行过程
  • 宏任务是在DOM渲染后触发,微任务是在DOM渲染前触发

常见微任务和宏任务

  • 常见的微任务: Promise……then、 async/await
  • 常见的宏任务: setTimeout、setInterval

2、async await介绍

async/await 是ECMAScript 2017提出的内容。但事实上它们只是Promise的语法糖。

因为Promise的出现主要是为了解决异步的回调地狱问题。将噩梦般的嵌套回调变为了优雅的管道式回调。但这始终是逃不掉“回调”二字。而async/await虽说只是Promise的语法糖,但让你“脱离”了回调,拥抱了同步代码

1.执行async函数,返回的是Promise对象

2.await必须在async包裹之下执行

3.await相当于Promise的then并且同一作用域下await下面的内容全部作为then中回调的内容

4.try……catch可捕获异常,代替了Promise的catch

5.异步中先执行微任务,再执行宏任务
async function fn() {
  return '我是async函数';
}

console.log('async:', fn());
输出结果:
async: Promise { '我是async函数' }

这里的fn() 相当于 Promise.resolve(‘我是async函数’)

(async function() {
  const p = Promise.resolve('success');
  const data = await p; // await就相当于Promise.then, 故data就是then的参数
  console.log(data);    // 这里的代码为then中回调的内容
})();
输出结果:
success

上面的这段代码,如果把async删掉,结果一定会报错!

(async function() {
  const p = Promise.reject('err');
  // await + try...catch 相当于 Promise.catch
  try {
    const res = await p;
    console.log(res);
  } catch(ex) {  // ex 来源于reject()里面的数
    console.error(ex); 
  }
})();
输出结果:
err

上面的代码中:await + try…catch 相当于 Promise.catch

3、同步,异步,微任务,宏任务的执行顺序

先同后异,先微后宏

  • 例子如下:
async function async1 () {
  console.log('async1 start');      // 2
  await async2();
  console.log('async1 end');        // 6
}
async function async2 () {
  console.log('async2');            // 3
}
console.log('script start');        // 1
setTimeout(function () {
  console.log('setTimeout');        // 8
}, 0)
async1()
new Promise (function (resolve) {
  console.log('promise1')           // 4
  resolve()
}).then(function () {
  console.log('promise2')           // 7
})

console.log('script end')           // 5
输出结果:
script start
async1 start
async2
promise1
script end
async1 end
promise2
setTimeout
  • 代码分析:
1. 先执行同步代码。
2. 所以首先执行 console.log('script start');
3. setTimeout为宏任务,先不执行 
4. 执行async1函数 console.log('async1 start'); 以及 async2(); await由于是Promise.then的语法糖是异步代码,先不执行 
5. new Promise() 内部代码要执行,后面的then的内容为微任务先不执行
6.执行console.log('script end')
7.同步代码执行结束 
8.开始按代码顺序执行微任务
9.先执行 console.log('async1 end'); 前面说过,await下面的代码相当于then里回调的内容
10.new Promise.then里面的内容 console.log('promise2')
11. 最后执行 宏任务代码,即setTimeout里的内容。

四、promisify

1、 Callback 与 Promise 间的桥梁 —— promisify

1.npm 安装
npm install bluebird

2. Node.js后端引入(Common.js规范)
var Promise = require("bluebird");

3.或者在 ES6import * as Promise from "bluebird";

4.如果那个 ES6 导入[不起作用](https://github.com/petkaantonov/bluebird/pull/1594)
import {Promise} from "bluebird";

Promise 很好地解决了异步方法的回调地狱、提供了我们在异步方法中使用 return 的能力,并将 callback 的调用纳入了自己的管理,而不是交给异步函数后我们就无能为力了(经常有 callback 被莫名调用两次而导致程序出错)。

2、promisify介绍

就是“promise 化”,将一个不是promise的方法变成 promise 。

  • 作用

将原本需要通过传入回调参数来实现回调执行(或者叫同步执行)改为利用promise.then的方式来调用,从而实现逻辑上的同步操作

const fs = require('fs');
const Promise = require('bluebird');

// 原有的callback调用
fs.readFile('input02.txt', function(err, data) {
  if (!err) {
      console.log('===: ' + data.toString());
  } else {
      console.log(err);
  }
});

// promisify后
var readFileAsync = Promise.promisify(fs.readFile);
readFileAsync('input01.txt').then(data => {
    console.log('---: ' + data.toString());
}, err => {
    console.log(err);
});

输出结果:
===: www.baidu.com

---: 我是通过fs.writeFile 写入文件的内容

这两个方法效果上是等价的。后者掌控性更好!

那么什么样的方法可以通过 promisify 变成 promise 呢?这里就需要介绍一个名词,nodeCallback。什么样的 callback 叫 nodeCallback ?

  • nodeCallback

有两个条件:1. 回调函数在主函数中的参数位置必须是最后一个;2. 回调函数参数中的第一个参数必须是 error 。

回调函数在主函数中的参数位置

// 正确
function main(a, b, c, callback) {  
}
 
// 错误
function main(callback, a, b, c) {   
}

回调函数参数中的第一个参数必须是 error

// 正确
function callback(error, result1, result2) {  
}
 
// 错误
function callback(result1, result2, error) {   
}

通过 nodeCallback ,我们定义了一个能被 promisify 的函数的格式,即,满足 nodeCallback 形式的方法,我们可以通过 promisify 来让它变成一个返回 promise 的方法。

3、promisify使用

const Promise = require('bluebird');
async function main(a, b, callback) {
  const array1 = a.concat(b);
  console.log('a=' + a);
  console.log('b=' + b);
  return callback(null, array1);
}
async function foo(c, d, callback) {
  const array2 = c.concat(d);
  console.log('c=' + c);
  console.log('d=' + d);
  callback(null, array2);
}

async function fu(a,b) {
  // Promise.promisify 将不是 promise 的方法转为 promise 方法(实现同步), 如果有 .then() 则为异步。
  const function1 = Promise.promisify(main);
  const function2 = Promise.promisify(foo);

  const data1 = await function1(a,b);   // await就相当于Promise.then, 故data就是then的参数
  console.log('data1:' + data1);        // 这里的代码为then中回调的内容
  for(let i = 0; i < data1.length; i++) {
    const data = data1[i];
    const data2 = await function2(data1, b);
    console.log('data2:' + data2);
  }
}

const arr1 = [2,3,4,5];
const arr2 = [45,6,7,8];
fu(arr1,arr2);
输出:
a=2,3,4,5
b=45,6,7,8
data1:2,3,4,5,45,6,7,8
c=2,3,4,5,45,6,7,8
d=45,6,7,8
data2:2,3,4,5,45,6,7,8,45,6,7,8
c=2,3,4,5,45,6,7,8
d=45,6,7,8
data2:2,3,4,5,45,6,7,8,45,6,7,8
c=2,3,4,5,45,6,7,8
d=45,6,7,8
data2:2,3,4,5,45,6,7,8,45,6,7,8
c=2,3,4,5,45,6,7,8
d=45,6,7,8
data2:2,3,4,5,45,6,7,8,45,6,7,8
c=2,3,4,5,45,6,7,8
d=45,6,7,8
data2:2,3,4,5,45,6,7,8,45,6,7,8
c=2,3,4,5,45,6,7,8
d=45,6,7,8
data2:2,3,4,5,45,6,7,8,45,6,7,8
c=2,3,4,5,45,6,7,8
d=45,6,7,8
data2:2,3,4,5,45,6,7,8,45,6,7,8
c=2,3,4,5,45,6,7,8
d=45,6,7,8
data2:2,3,4,5,45,6,7,8,45,6,7,8

代码说明:fu()函数中,将main()函数和foo()函数进行了promisify化。将asycn await函数转换为 promise 函数。其中callback中,需要给第一个参数给为:null。如果不为null会有如下错误:

const Promise = require('bluebird');
async function main(a, b, callback) {
  const array1 = a.concat(b);
  console.log('a=' + a);
  console.log('b=' + b);
  return callback(array1); 
}
async function foo(c, d, callback) {
  const array2 = c.concat(d);
  console.log('c=' + c);
  console.log('d=' + d);
  callback(null, array2);
}

async function fu(a,b) {
  // Promise.promisify 将不是 promise 的方法转为 promise 方法(实现同步), 如果有 .then() 则为异步。
  const function1 = Promise.promisify(main);
  const function2 = Promise.promisify(foo);

  const data1 = await function1(a,b);   // await就相当于Promise.then, 故data就是then的参数
  console.log('data1:' + data1);        // 这里的代码为then中回调的内容
  for(let i = 0; i < data1.length; i++) {
    const data = data1[i];
    const data2 = await function2(data1, b);
    console.log('data2:' + data2);
  }
}

const arr1 = [2,3,4,5];
const arr2 = [45,6,7,8];
fu(arr1,arr2);

错误信息:

a=2,3,4,5
b=45,6,7,8
node:internal/process/promises:265
            triggerUncaughtException(err, true /* fromPromise */);
            ^

[UnhandledPromiseRejection: This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled 
with .catch(). The promise rejected with the reason "[object Array]".] {
  code: 'ERR_UNHANDLED_REJECTION'
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值