Promise初探

promise是什么

借用ECMAScript 6入门中对其的解释: Promise 是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强大。它由社区最早提出和实现,ES6 将其写进了语言标准,统一了用法,原生提供了Promise对象。

所谓Promise,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。从语法上说,Promise 是一个对象,从它可以获取异步操作的消息。Promise 提供统一的 API,各种异步操作都可以用同样的方法进行处理。

谁在使用promise

在es6尚未提供原生的Promise之前,一些库入Q blueBird已经实现了Promimse达到的效果,解决异步编程的问题。也可以自己实现promise,但是要符合promiseA+规范

为什么使用promise

Promise的作用

解决了 “回调地狱”问题 解决了 异步执行结果无法同步获取 既然说Promise解决了这两个痛点,那么就先看一下Promise之前这两个问题是如何实现的。

回调地狱

回调地狱:常见的ajax、事件监听,在node中文件读取都属于异步编程,回调函数作为参数存在于这一异步操作的函数中,条件满足的时候会在异步操作中触发回调函数的执行。实际使用中回调可能存在多层嵌套,外部的回调依赖内部回调执行完成的结果,层层依赖,代码可读性降低。例如:

let fs = require('fs');
fs.readFile('1.txt','utf8',function(err,data){
    fs.readFile(data,'utf8',function(err,data){
        fs.readFile(data,'utf8',function(err,data){
            console.log(data);
        });
    });
});
复制代码
同步异步结果

同步异步结果:很多情况同时发送多个异步请求,但是只有结果全部返回才继续执行。之前的实现方案有两种。1)制定计数器2)发布订阅

制定计数器

方法:制定计数器,每次调用次数递增,发现全部回调完成,输出结果。

function after (times,callback){
    return function(){
        times--;
        if(times == 0){
            callback();
        }
    }
}
let fn = after(3,function(){
    console.log('调用了三次 才执行的方法')
})
fn();
fn();
fn();// 调用三次才执行的方法
复制代码
发布订阅

方法:先把结果暂存(订阅),满足条件时一次执行(发布)

let fs = require('fs');
let event = {
    arr:[],
    result:[],
    on(fn){
        this.arr.push(fn);
    },
    emit(data){
        this.result.push(data);
        this.arr.forEach(fn=>fn(this.result));
    }
}
event.on(function(data){
    if(data.length === 2){
        console.log(data); // 最终的结果
    }
});
fs.readFile('1.txt','utf8',function(err,data){ 
    event.emit(data);
})
fs.readFile('2.txt','utf8',function(err,data){ 
    event.emit(data);
});
复制代码

promis的基本用法

了解了以上,我们开始Promise

promise共有三种状态 等待态(pending)成功态(resolved)失败态(rejected) 状态的转化 pending -> resolved pending -> rejected resolved和rejected不能相互转化,状态只会更改一次

我们写一个简单的Promise

const promise = new Promise(function(resolve, reject) {
  // ... some code
  if (/* 异步操作成功 */){
    resolve(value);
  } else {
    reject(error);
  }
});
promise.then(function(value) {
  // success
}, function(error) {
  // failure
});
复制代码

解析:Promise构造函数接受一个函数作为参数,该函数的两个参数分别是resolve和reject。它们是两个函数,由 JavaScript 引擎提供,不用自己部署。

resolve函数的作用是,将Promise对象的状态从“等待”变为“成功”(即从 pending 变为 resolved),在异步操作成功时调用,并将异步操作的结果,作为参数传递出去;reject函数的作用是,将Promise对象的状态从“等待”变为“失败”(即从 pending 变为 rejected),在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。

Promise实例生成以后,可以用then方法分别指定resolved状态和rejected状态的回调函数。

说了痛点,说了好的解决方案,那就用好的解决方案实现一下。

解决“回调地狱”

let fs = require('fs');

function read(filePath,encoding){
    return new Promise((resolve,reject)=>{
        fs.readFile(filePath,encoding,(err,data)=>{
            if(err) reject(err);
            resolve(data);
        });
    })
}
read('1.txt','utf8').then(data=>{
    return read(data,'utf8')
}).then((data)=>{
    return read(data,'utf8');
}).then((data)=>{
    return read(data,'utf8');
}).then((data)=>{
    console.log(data);
})
复制代码

解决同步异步结果

let fs = require('fs');
function read(filePath, encoding) {
    return new Promise(function (resolve, reject) {
        fs.readFile(filePath, encoding, function (err, data) {
            if(err) reject(err);
            resolve(data);
        })
    });
}
Promise.all([read('1.txt', 'utf8'), read('2.txt', 'utf8')]).then(data => {
    console.log(data);
})
复制代码

从刚刚的例子中我们看到promise对代码逻辑做了简化。先从个api入手了解一下promise的功能。

Promise.prototype.then()

每个promise的实例 都有then方法,也就是说,then方法是定义在原型对象Promise.prototype上的它的作用是为 Promise 实例添加状态改变时的回调函数。前面说过,then方法的第一个参数是resolved状态的回调函数,第二个参数(可选)是rejected状态的回调函数。 then方法返回的是一个新的Promise实例(注意,不是原来那个Promise实例)。因此可以采用链式写法,即then方法后面再调用另一个then方法。

unction timeout(ms) {
    return new Promise((resolve, reject) => {
        setTimeout(function () {
            resolve('done');
        }, ms)
    });
}

timeout(100).then((value) => {
    console.log(value);
    Promise.reject('no')
}).then(null,(err)=>{
    console.log(err)
}).catch((err)=>{
    console.log(err);
})
// 先打印done 后进入到下个then的err环节里
复制代码

像上例链式的调用then,第一个回调函数完成以后,会将返回结果作为参数,传入第二个回调函数。

Promise.prototype.catch()

Promise.prototype.catch方法是.then(null, rejection)的别名,用于指定发生错误时的回调函数。catch方法返回的还是一个 Promise 对象,因此后面还可以接着调用then方法。

// 写法一
const promise = new Promise(function(resolve, reject) {
  try {
    throw new Error('test');
  } catch(e) {
    reject(e);
  }
});
promise.catch(function(error) {
  console.log(error);
});

// 写法二
const promise = new Promise(function(resolve, reject) {
  reject(new Error('test'));
});
promise.catch(function(error) {
  console.log(error);
});
复制代码

Promise.all()

Promise.all方法用于将多个 Promise 实例,包装成一个新的 Promise 实例。是类上的方法,属于promise的语法糖

const p = Promise.all([p1, p2, p3]);
复制代码

上面代码中,Promise.all方法接受一个数组作为参数,p1、p2、p3都是 Promise 实例,如果不是,就会先调用下面讲到的Promise.resolve方法,将参数转为 Promise 实例,再进一步处理。(Promise.all方法的参数可以不是数组,但必须具有 Iterator 接口,且返回的每个成员都是 Promise 实例。)

p的状态由p1、p2、p3决定,分成两种情况。

(1)只有p1、p2、p3的状态都变成fulfilled,p的状态才会变成fulfilled,此时p1、p2、p3的返回值组成一个数组,传递给p的回调函数。

(2)只要p1、p2、p3之中有一个被rejected,p的状态就变成rejected,此时第一个被reject的实例的返回值,会传递给p的回调函数。
之前解决同步异步结果就是使用promise.all()在这里不赘述了。

Promise.resolve()

有时需要将现有对象转为 Promise 对象,Promise.resolve方法就起到这个作用。

Promise.resolve('foo')
// 等价于
new Promise(resolve => resolve('foo'))
复制代码

Promise.reject()

Promise.reject(reason)方法也会返回一个新的 Promise 实例,该实例的状态为rejected。

const p = Promise.reject('出错了');
// 等同于
const p = new Promise((resolve, reject) => reject('出错了'))

p.then(null, function (s) {
  console.log(s)
});
// 出错了
复制代码

总结

本文只作为初学者的参考,了解promise的来由及基本的用法。本人也仅仅是个初学者,有问题可以一起讨论和补充。promise的各个api的深入使用和源码实现,后续深入了解后继续补充

参考文献

  1. 阮一峰es6-promise

转载于:https://juejin.im/post/5b5d199ce51d455d6825dff4

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值