给自己一个微笑

给自己一个微笑 查看完整档案

填写现居城市  |  填写毕业院校  |  填写所在公司/组织填写个人主网站
编辑
_ | |__ _ _ __ _ | '_ \| | | |/ _` | | |_) | |_| | (_| | |_.__/ \__,_|\__, | |___/ 该用户太懒什么也没留下

个人动态

给自己一个微笑 发布了文章 · 5月9日

微信小程序 扩展组件 tabs 中的 swiper 部分高度自适应

由于官方给了 swiper 固定高度150,且 swiper-item 都是 absolute 定位,所以实际应用中经常会碰到问题,在此记录一下修改方式。

一、正常 npm 下载 tabs 包,并用开发者工具构建 npm

二、修改构建出的目录 miniprogram_npm 目录下的 tabs 插件源码

  • 1、修改 tabs 插件的 wxml 文件,添加 style 属性,看下图位置

image.png

  • 2、修改 tabs 插件的 js 文件,添加 swiperStyle 属性

image.png

  • 3、使用 tabs 插件的页面添加 swiperStyle 属性,并动态计算高度赋值
<mp-tabs 
  tabs="{{tabs}}" 
  activeTab="{{activeTab}}" 
  swiperClass="weui-tabs-swiper"
  bindtabclick="onTabCLick"
  bindchange="onChange"
  activeClass="tab-bar-title__selected"
  swiperStyle="height: {{tabSwiperHeight}}px"
>
  <block wx:for="{{tabs}}" wx:for-item="tab" wx:for-index="index" wx:key="index">
    <view class="tab-content tab-content-{{index}}" slot="tab-content-{{index}}" >
        {{tab.title}}
    </view>
  </block>
</mp-tabs>
Page({
  data: {
    tabs: [{title: '首页'}, {title: '外卖'}, {title: '商超生鲜'}, {title: '购物'}, {title: '美食饮品'}, {title: '生活服务'}, {title: '休闲娱乐'}],
    activeTab: 0,
    tabSwiperHeight: 0
  },
  tabsSwiperHeight() {
    // tab 组件内的swiper高度自适应问题
    let index = this.data.activeTab;
    let queryDom = wx.createSelectorQuery()
    queryDom.select('.tab-content-' + index).boundingClientRect().exec(rect => {
      this.setData({
        tabSwiperHeight: rect[0].height
      })
    })
  },
  onTabCLick(e) {
    const index = e.detail.index
    this.setData({activeTab: index})
  },
  onChange(e) {
    const index = e.detail.index
    this.setData({activeTab: index})
    this.tabsSwiperHeight();
  }
}
查看原文

赞 2 收藏 0 评论 5

给自己一个微笑 发布了文章 · 4月27日

html2canvas 使用记录及注意点

在网上看到的总是差一点,在此记录一下自己需要的

自己的需求

  • 有图片和文字组合展示
  • 图片是链接,需要异步加载
  • 只展示组合后 html2canvas 生成的图片,即抓取的 dom 结构部分要隐藏

直接上代码

下边都是 vue 模式代码,需要注意注释的地方是不能丢的
<!-- HTML 部分 -->
<div class="op">
  <div class="show-view" ref="showView">   <!-- 要抓取的 dom 部分 -->
    <p>这里是文字文字叮当的。。。。</p>
    <img data-original="图片地址" alt="">
  </div>
</div>
<div class="canvas-view">
  <!-- 存放生成的图片部分 -->
  <img :data-original="canvasSrc" alt=""> 
</div>
// css 部分
.op {
  opacity: 0;  // 要隐藏要抓取 dom 部分,只能是设置到抓取 dom 的外层元素上。
  .show-view img{
    width: 200px;
    height: 300px;   // 异步加载图片高度一定要设置好,否则会抓取不到
  }
}
// 部分 js
import html2canvas from 'html2canvas';
export default {
  data () {
    return {
      canvasSrc: ''
    }
  },
  mounted () {
    let dom = this.$refs.showView;
    html2canvas(dom, {
        useCORS: true, // 有异步加载部分,例如图片等
        scale: 2, // 设备像素比,默认是设备的 devicePixelRatio
        height: dom.clientHeight, // 生成的 canvas 部分的高度
        width: dom.clientWidth // 生成的 canvas 部分的宽度
      }).then(canvas => {
        // canvas 参数即是抓取的 show-view 部分dom生成的canvas,后边是否需要生成图片,看自己的需求
        let src  = canvas.toDataURL(); // 调用 canvas 的方法,生成图片
        this.canvasSrc = src;
      });
  }
}
查看原文

赞 0 收藏 0 评论 0

给自己一个微笑 发布了文章 · 2019-05-22

CORS记录

CORS 详解

CORS:跨域资源共享机制。使用额外的 HTTP 头来告诉浏览器 让运行在一个 origin (domain) 上的Web应用被准许访问来自不同源服务器上的指定的资源。当一个资源从与该资源本身所在的服务器不同的域、协议或端口请求一个资源时,资源会发起一个跨域 HTTP 请求。要想实现CORS跨域,就需要服务器和客户端都做一些配置。

服务端配置

以node服务端为例,使用了express。主要代码如下:

const express = require('express');
const app = express();
app.get('/', function (req, res) {
  res.header('Access-Control-Allow-Origin', 'http://dev.ganji.com:8008');
  res.header('Access-Control-Allow-Credentials', true);
  res.header('Access-Control-Expose-Headers', 'Date');
  res.header('Access-Control-Request-Headers', 'X-Custom-Header');
  res.header('Access-Control-Allow-Methods', 'GET,POST,OPTIONS,PUT');
  res.json({
    ...
  })
})
var server = app.listen(8081, function () {})

下面对配置项简单介绍:

Access-Control-Allow-Origin

允许跨域访问域名,请求端的域名或者 *,* 表示任意域名都可以访问。

Access-Control-Allow-Credentials

跨域后默认不携带cookie和http的认证信息的,所以就需要添加配置,表示服务器明确许可携带cookie。此时需要注意Origin不能设置为 *, 必须明确指定与请求网页域名一致。
服务端:

res.header('Access-Control-Allow-Credentials', true);

客户端需要在请求中添加withCredentials属性:

var xhr = new XMLHttpRequest();
xhr.withCredentials = true;

请求信息
图片描述

Access-Control-Expose-Headers

默认xhr对象的response header中只能拿到六个基本字段:Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma,可以在此设置想要拿到的其他字段。

res.header('Access-Control-Expose-Headers', 'Date');
简单请求

简单请求条件:
1、请求方法

  • GET
  • POST
  • HEAD

2、头信息不能超过下边范围

  • Accept
  • Accept-Language
  • Content-Language
  • Content-Type 取值范围为:

    • text/plain
    • multipart/form-data
    • application/x-www-form-urlencoded

3、XMLHttpRequestUpload 中没有注册事件监听器

简单请求的客户端和服务端请求过程:
图片描述

复杂请求

如果不满足简单请求的条件,则会发送复杂请求。复杂请求会先用OPTIONS方法发起一个预检请求到服务器,服务器允许后,再发送真实请求。
过程下图:
图片描述
实际请求信息:
第一次的预检请求:
图片描述
第二次实际请求:
图片描述
复杂请求配置介绍:

Access-Control-Request-Method

复杂请求时必须配置,用来指定浏览器的CORS请求会用到哪些HTTP方法,上图中的 PUT。

res.header("Access-Control-Allow-Methods","PUT")
Access-Control-Request-Headers

浏览器CORS请求会额外发送的头信息字段。逗号分隔的字符串。

总结

CORS是服务端和浏览器配合完成的跨域请求,感觉主要是服务端配置好后,浏览器根据服务端的配置的头部和提供的可用的CORS方法来实现跨域。

查看原文

赞 0 收藏 0 评论 0

给自己一个微笑 发布了文章 · 2019-05-22

CORS记录

CORS 详解

CORS:跨域资源共享机制。使用额外的 HTTP 头来告诉浏览器 让运行在一个 origin (domain) 上的Web应用被准许访问来自不同源服务器上的指定的资源。当一个资源从与该资源本身所在的服务器不同的域、协议或端口请求一个资源时,资源会发起一个跨域 HTTP 请求。要想实现CORS跨域,就需要服务器和客户端都做一些配置。

服务端配置

以node服务端为例,使用了express。主要代码如下:

const express = require('express');
const app = express();
app.get('/', function (req, res) {
  res.header('Access-Control-Allow-Origin', 'http://dev.ganji.com:8008');
  res.header('Access-Control-Allow-Credentials', true);
  res.header('Access-Control-Expose-Headers', 'Date');
  res.header('Access-Control-Request-Headers', 'X-Custom-Header');
  res.header('Access-Control-Allow-Methods', 'GET,POST,OPTIONS,PUT');
  res.json({
    ...
  })
})
var server = app.listen(8081, function () {})

下面对配置项简单介绍:

Access-Control-Allow-Origin

允许跨域访问域名,请求端的域名或者 *,* 表示任意域名都可以访问。

Access-Control-Allow-Credentials

跨域后默认不携带cookie和http的认证信息的,所以就需要添加配置,表示服务器明确许可携带cookie。此时需要注意Origin不能设置为 *, 必须明确指定与请求网页域名一致。
服务端:

res.header('Access-Control-Allow-Credentials', true);

客户端需要在请求中添加withCredentials属性:

var xhr = new XMLHttpRequest();
xhr.withCredentials = true;

请求信息
图片描述

Access-Control-Expose-Headers

默认xhr对象的response header中只能拿到六个基本字段:Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma,可以在此设置想要拿到的其他字段。

res.header('Access-Control-Expose-Headers', 'Date');
简单请求

简单请求条件:
1、请求方法

  • GET
  • POST
  • HEAD

2、头信息不能超过下边范围

  • Accept
  • Accept-Language
  • Content-Language
  • Content-Type 取值范围为:

    • text/plain
    • multipart/form-data
    • application/x-www-form-urlencoded

3、XMLHttpRequestUpload 中没有注册事件监听器

简单请求的客户端和服务端请求过程:
图片描述

复杂请求

如果不满足简单请求的条件,则会发送复杂请求。复杂请求会先用OPTIONS方法发起一个预检请求到服务器,服务器允许后,再发送真实请求。
过程下图:
图片描述
实际请求信息:
第一次的预检请求:
图片描述
第二次实际请求:
图片描述
复杂请求配置介绍:

Access-Control-Request-Method

复杂请求时必须配置,用来指定浏览器的CORS请求会用到哪些HTTP方法,上图中的 PUT。

res.header("Access-Control-Allow-Methods","PUT")
Access-Control-Request-Headers

浏览器CORS请求会额外发送的头信息字段。逗号分隔的字符串。

总结

CORS是服务端和浏览器配合完成的跨域请求,感觉主要是服务端配置好后,浏览器根据服务端的配置的头部和提供的可用的CORS方法来实现跨域。

查看原文

赞 0 收藏 0 评论 0

给自己一个微笑 提出了问题 · 2019-05-21

node搭建服务,hosts指向了域名,跨域cookie取不到

问题描述

node启动了两个服务,一个用来提供接口,一个用来存放前端页面,用了hosts指向了两个域名,配置了header的相关配置后,取到的不是前端页面(浏览器)的cookie,取到的是接口(服务端)的cookie。

问题出现的环境背景及自己尝试过哪些方法

不用hosts指向,直接用ip访问的就可以取到浏览器的cookie

相关代码

// 服务端代码
const express = require('express');
const app = express();
app.get('/user', function (req, res) {
  res.header("Access-Control-Allow-Origin", 'http://dev.page.com:8082'); //需要显示设置来源,不能使用‘*’
  res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
  res.header("Access-Control-Allow-Methods","PUT,POST,GET,DELETE,OPTIONS");
  res.header("Access-Control-Allow-Credentials",true);
  res.json({
    method: 'GET - user',
    cookies: req.headers.cookie || '',
    params: req.params,
    path: req.path,
    query: req.query,
    route: req.route
  })
})
var server = app.listen(8081, function () {})

// 接口请求
$.ajax({
    url: 'http://dev.interface.com:8081/user',
    method: 'GET',
    xhrFields: {
        withCredentials: true
    },
    crossDomain: true,
    success: function(res){
        console.log(res);
    },
    fail: function(err){
        console.log(err);
    }
})
// 配置hosts
127.0.0.1 dev.page.com
127.0.0.1 dev.interface.com

你期待的结果是什么?实际看到的错误信息又是什么?

希望能在request header中自动带上浏览器cookie。
下图是实际的接口请求的结果截图
图片描述

关注 3 回答 2

给自己一个微笑 提出了问题 · 2019-05-21

node搭建服务,hosts指向了域名,跨域cookie取不到

问题描述

node启动了两个服务,一个用来提供接口,一个用来存放前端页面,用了hosts指向了两个域名,配置了header的相关配置后,取到的不是前端页面(浏览器)的cookie,取到的是接口(服务端)的cookie。

问题出现的环境背景及自己尝试过哪些方法

不用hosts指向,直接用ip访问的就可以取到浏览器的cookie

相关代码

// 服务端代码
const express = require('express');
const app = express();
app.get('/user', function (req, res) {
  res.header("Access-Control-Allow-Origin", 'http://dev.page.com:8082'); //需要显示设置来源,不能使用‘*’
  res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
  res.header("Access-Control-Allow-Methods","PUT,POST,GET,DELETE,OPTIONS");
  res.header("Access-Control-Allow-Credentials",true);
  res.json({
    method: 'GET - user',
    cookies: req.headers.cookie || '',
    params: req.params,
    path: req.path,
    query: req.query,
    route: req.route
  })
})
var server = app.listen(8081, function () {})

// 接口请求
$.ajax({
    url: 'http://dev.interface.com:8081/user',
    method: 'GET',
    xhrFields: {
        withCredentials: true
    },
    crossDomain: true,
    success: function(res){
        console.log(res);
    },
    fail: function(err){
        console.log(err);
    }
})
// 配置hosts
127.0.0.1 dev.page.com
127.0.0.1 dev.interface.com

你期待的结果是什么?实际看到的错误信息又是什么?

希望能在request header中自动带上浏览器cookie。
下图是实际的接口请求的结果截图
图片描述

关注 3 回答 2

给自己一个微笑 赞了文章 · 2019-05-13

generator的基本使用及co库的原理

通常ajax请求都会用到异步调用问题,为了保证调用顺序的正确性,一般会用到callback,后来出现了promise的解决方案。
在异步编程中,还有一种解决方案,那就是generator生成器函数,这个函数会返回一个迭代器。

特点:函数声明只是多了个*号来表示这是一个生成器函数,并且一般可以在函数内看到yield关键字。
原理:它会将一个函数分为若干个小函数,并且返回一个迭代器,通过调用迭代的next方法(移动指针),内部遍历值、状态,保持正确的执行顺序。每次调用next方法,都会向下执行一步。
用法:
1.先产出,下一个next负责传值,然后接收新值。
2.yield后面跟着的是value值(产出值)。 yield等号前面的 是我们当前调用next传进来的 值。没有等号代表不接收新值。 注意点:迭代器的第一个next执行,传值是无效的。
function *read(){
    console.log(1);
    var content1 = yield 'qs';
    console.log(content1);
    var content2 = yield '9';![图片描述][1]
    console.log(content2);
    return content2;
}
var it = read();
var a = it.next() //输出 1  a:{value:'qs',done:false}
var b = it.next('hello')//输出hello  b:{value:'9',done:false}
var c = it.next('generator')//输出generator  c:{value:'generator',done:true}
var d = it.next('123')//输出generator  c:{value:undefined,done:true}
console.log(a,b,c,d)

具体的执行步骤,见图分解图片描述

产出的值 是一个object,包含两个key :value 和 done value
表示产出的值,done表示执行状态(迭代器是否执行完成,全部遍历一遍) 当done
为true时表示遍历完成。之后再次执行next,就没有产出了,所以value为undefined,done仍旧是true;

用处:generator一般和promise结合一起用

let fs = require('fs');
let blueBird =  require('bluebird');
let read2 = blueBird.promisify(fs.readFile);
function *r(){
    let content1 = yield read2('./1.txt','utf8');
    let content2 = yield read2(content1,'utf8');
    return content2
}
let it2 = r();
it2.next().value.then(res=>{
   it2.next(res).value.then(res=>{
        console.log(res);
        console.log(it2.next(res));//需要给content2赋值,然后产出{ value: '大大大', done: true }
    })
});

为了更好的结合promise使用,出现了co库。
co库的作用就是:把一个生成器函数的迭代器,最后一步执行完毕后,然后统一执行一个成功回调
安装:npm install co --save


let co = require('co');
function *r2(){
    let content1 = yield read2('./1.txt','utf8');
    let content2 = yield read2(content1,'utf8');
    return content2
}
co(r2).then(function(res){console.log(res,11111111111111)});

co库实现原理,就是利用产出值的done的状态,去判断是否需要再次递归执行next方法。

function co(it){
    return new Promise(function(resolve,reject){
        function next(data){
            let {value,done}= it.next(data);
            if(!done){
                value.then(res=>{
                    next(res);
                },reject)
            }else{
                resolve(value)
            }
        }
        next();
    })
}
查看原文

赞 1 收藏 1 评论 0

给自己一个微笑 关注了用户 · 2019-04-25

乔实 @qiaoshi

好记性不如烂笔头,我没笔,只有键盘

关注 2

给自己一个微笑 赞了文章 · 2018-03-21

js处理异步函数:从callback到promise

函数的执行分为同步和异步两种。
同步即为 同步连续执行,通俗点讲就是做完一件事,再去做另一件事。
异步即为 先做一件事,中间可以去做其他事情,稍后再回来做第一件事情。
同时还要记住两个特性:1.异步函数是没有返回值的,return不管用哦 2.try{}catch(e){}不能捕获异步函数中的异常。

js在处理异步回调函数的情况有着越来越值得推崇的方法及类库,下面会依次介绍js处理异步函数的发展史,及源码解读。
(本文代码是运行在node环境中)

1.callback

let fs = require('fs');
fs.readFile('./1.txt','utf8',function(err,data){
    console.log(data);
})

如果只有一个异步请求,那用callback还好,但是相信大多数前端开发者都遇到过这两种情况:
a.一个异步请求获取到的结果是下一个异步请求的参数。(一直嵌套callback,代码不好管理会形成回调地狱);

let fs = require('fs');
    fs.readFile('./1.txt','utf8',(err,data)=>{
        fs.readFile(data,'utf8',(err,data)=>{
            console.log(data);
        })
    })

b.发出两个请求,只有当两个请求都成功获取到数据,在执行下一步操作。

let fs =require('fs');
    fs.readFile('./1.txt','utf8',(err,data)=>{
        console.log(data);
    })
    fs.readFile('./2.txt','utf8',(err,data)=>{
        console.log(data);
    })

像类似这种情况,只有当读取到1.txt 和2.txt的文件的时候,我们同时获取到两个异步请求的结果。我们可以写一个计数器的函数,统一处理回调;

function after(time,callback){
    let arr = [];
    return function(data){
        arr.push(data)
        if(--time==0){
            callback(arr);
        }
    }
}
  //统一处理回调结果的回调传到after函数中。
  let out = after(2,(res)=>{console.log(res)});
  let fs =require('fs');
    fs.readFile('./1.txt','utf8',(err,data)=>{
        out(data);
    })
    fs.readFile('./2.txt','utf8',(err,data)=>{
        out(data);
    })

tips:

方便我们更好的了解计数器的实现原理,我们需要了解一个概念:高阶函数
高阶函数:可以把函数作为参数 或者 return返回出一个函数。
举个例子:

①.判断一个变量是不是属于一个类型:

function isType(type,content){
   return Object.protoType.toString.call(content) ==`[Object ${type}]`
}
let a = [1,2,3];
isType('Array', a) == true;

②.js数据类型有好多,我们每次调用都要传入他的类型,麻不麻烦。所以我们写一个方法,可以批量生成函数。

function isType(type){
    return function(content){
        return Object.protoType.toString.call(content) == `[Oject ${type}]`
    }
}
let isArray = isType('Array');
let a = [1,2,3]
isArray(a);

前两种示例讲的是return返回一个函数,下面示例是一个预置函数及返回函数参数的结合示例(预置函数)。

③.场景加入我有一个函数,执行第三次的时候我想输出'我很可爱';平常我们可以这样去实现:

let time =0;
     function say(){
         if(++item==3){
         console.log('我很可爱')
         }
     }
     say();
     say();
     say();

高阶函数实现的话:

 function after(time,callback){
        return function(){
            if(--time ==0){
                   callback();
            }
        }
     }
     function say(){
         console.log('我很可爱');
     }
     let out =after(3,say)
     out();
     out();
     out();

高阶函数实现了将计时任务与业务逻辑拆分,高阶函数的实现主要得益于作用域的查找。

2.Promise

在看完了上面的callback讲述,主要其实还是讲述了callback的弊端:
a.回调地狱(callback无法解决)
b.并发请求,同时拿到结果(可通过计数器方式,但是太费劲,不太乐观)
这个时候duang~duang~duang~,ES6带着Promise来了~
Promise主要是es6提供的主要用于处理异步请求的一个对象,他能够很好的解决回调地狱以及并发请求。
在写promise源码之前,我们先通过几个调用promise的示例,了解一下promise的一些原理及特性,这在我们封装promise的时候能够起到很大的作用:
普通调用实例:

let fs = require('fs');
let p = new Promise(function(resolve,reject){
  fs.readFile('./1.txt','utf8',(err,data)=>{
      err?reject(err):resolve(data);
  })
})

p.then((data)=>{console.log(data)},(err)=>{console.log(err)});

1.promise实例可以多次调用then方法;

  p.then((data)=>{console.log(data)},(err)=>{console.log(err)});
  p.then((data)=>{console.log(data)},(err)=>{console.log(err)});

2.promise实例可以支持then方法的链式调用,jquery实现链式是通过返回当前的this。但是promise不可以通过返回this来实现。因为后续通过链式增加的then不是通过原始的promise对象的状态来决定走成功还是走失败的。

p.then((data)=>{console.log(data)},(err)=>{console.log(err)}).then((data)=>{console.log(data)})

3.只要then方法中的成功回调和失败回调,有返回值(包括undefiend),都会走到下个then方法中的成功回调中,并且把返回值作为下个then成功回调的参数传进去。

第一个then走成功:
p.then((data)=>{return undefined},(err)={console.log()}).then((data)=>{console.log(data)})
输出:undefiend
第一个then走失败:
  p.then((data)=>{console.log(1)},(err)={return undefined).then((data)=>{console.log(data)})
输出:undefiend

4.只要then方法中的成功回调和失败回调,有一个抛出异常,则都会走到下一个then中的失败回调中;

第一个then走成功:
p.then((data)=>{throw new Err("错误")},(err)={console.log(1)}).then((data)=>{console.log('成功')},(err)=>{console.log(err)})
输出:错误
第一个then走失败:
  p.then((data)=>{console.log(1)},(err)={throw new Err("错误")).then((data)=>{console.log('成功')},(err)=>{console.log(err)})
输出:错误

5.成功和失败 只能走一个,如果成功了,就不会走失败,如果失败了,就不会走成功;

6.如果then方法中,返回的不是一个普通值,仍旧是一个promise对象,该如何处理?
答案:它会等待这个promise的执行结果,并且传给下一个then方法。如果成功,就把这个promise的结果传给下一个then的成功回调并且执行,如果失败就把错误传给下一个then的失败回调并且执行。

7.具备catch捕获错误;如果catche前面的所有then方法都没有失败回调,则catche会捕获到错误信息执行他就是用来兜儿底用的。

p是一个失败的回调:
p.then((data)=>{console.log('成功')}).then((data)=>{成功}).catche(e){console.log('错误')}

8.返回的结果和 promise是同一个,永远不会成功和失败

var  r  = new Promise(function(resolve,reject){
   return r;
})
r.then(function(){
    console.log(1)
},function(err){
    console.log(err)
})

以上是经过调用es6提供的promise,发现的一些特性,下面我们会根据这些特性去封装Promise类。

一.我们先通过初步了解的promise和简单的基本调用,简单的实现一个promise;

1.Promise支持传入一个参数,函数类型,这个函数往往是我们自己发起异步请求的函数,我们称它为执行器actuator,这个函数会在调用new Promise()的作用域内立即执行,并且传入两个函数一个resolve另一个是reject作为参数;

2.promise对象支持.then()的方法,then方法支持两个参数一个为onFulfilled成功回调另一个为onRejected失败回调;onFulfilled接受参数data为异步请求拿到的数据,onRejected接受的参数为捕获到的异常错误。

3.当异步回调成功时,执行resolve,并且把回调结果传给resolve函数。失败则执行reject,把异常信息传给reject函数。(这一步往往是在actuator执行器函数中我们自己去控制执行的)

4.一个promise对象,执行了resolve,就不会在去执行reject。执行了reject,也不会在去执行resolve;
所以promise内部中有一个类似状态机的机制,它分为三种状态,创建一个promise对象,默认状态为'pending'状态,当执行了resolve,则该状态变为'fulfilled',若果执行了reject则该状态变为'rejected',所以我们在then方法中需要根据状态作出判断;

5.promise对象已经是成功状态或是失败状态时,都可以继续通过then传入函数,会通过当前的状态,来决定执行成功还失败,并且把结果或是错误传给相应的函数。所以我们需要拿到的结果和捕获的错误。

    function Promise(fn){
    this.status = 'pending';//状态机
    //一个promise支持执行多个then,所以需要一个池子把他的回调函数存储起来,统一遍历执行;
    this.onFulfilledCallbacks = [];
    this.onRejectedCallbacks =[]; 
    //保存结果或者错误异常
    this.result = '';//当前promise回调成功获取到的数据;
    this.reason = '';//当前promise失败的原因
    var self = this;
    function resolve(data){
        //执行了reject就不能执行resolve,所以必须保证是pending状态;
        //当执行回调成功,在执行器调用resolve,我们去遍历成功回调的池子,依次执行;
        //保存结果,并且将当前状态设置为'fulfilled'
        if(self.status=='pending'){
            self.result = data;
            self.status = 'fulfilled';
            self.onFulfilledCallbacks.forEach((fn)=>{
                fn(data);
            })
        }
          
    }
    function reject(err){
        //执行了resolve就不能执行reject,所以必须保证是pending状态;
        //当执行回调失败,在执行器调用reject,我们去遍历成功回调的池子,依次执行;
        //保存错误原因并且将当前状态设置为'rejected'
        if(self.status=='pending'){
          self.reason= err;
          self.status ='rejected';
          self.onRejectedCallbacks.forEach((fn)=>{
              fn(err);
          })
        }
    }
    fn(resolve,reject)
}
Promise.prototype.then= function(onFulfilled,onRejected){
   //如果当前promise对象成功状态,则直接执行onFulfilled回调函数,并且把拿到的已经保存的成功数据传进去。
   if(this.status =='fulfilled'){
       onFulfilled(this.result)    
   }
   //如果当前promise对象失败状态,则直接执行rejected回调函数,并且把已经保存的补货失败的原因传进去。
   if(this.status =='rejected'){
       onRejected(this.reason);
   }
   if(this.status == 'pending'){
       this.onFulfilledCallbacks.push(onFulfilled);
       this.onRejectedCallbacks.push(onRejected);
   }
}

到目前为止我们已经封装了一个简易版的promise了,我们可以通过一些case去测试一下,是否满足上面所描述的特性。

let fs = require('fs');
let p = new Promise((resolve,reject)=>{
   fs.readFile('./1.txt','utf8',function (err,data) {
              err ? reject(err):resolve(data);
   })
});
p.then(data=>{console.log(data)},err=>{console.log(err)}); 
p.then(data=>{console.log(data)},err=>{console.log(err)});

二、我们简易版的promise类,已经初步实现了一些promise的基本特性;这一节我们我们简易版的promise进行改版,把promise的更复杂的功能增加进去。

1.当我们调用promise时,传入的执行器会立刻执行,执行器函数内部是一个同步的过程,我们可以用try...catch捕获错误,并且应该直接调用失败的函数。

2.promise支持链式写法,then后面继续.then ,原理并不是像jquery一样返回一个this;而是不管当前promise状态是什么,都返回一个新的promise对象,官方文档命名这个新的promise对象为promise2。
3.链式写法中第二个then中的回调走成功还是走失败,取决于上一个then中返回的promise(就是promise2)对象的状态。 而 promise2对象的状态,是由第一个then的参数(成功回调函数或失败回调函数)的返回值决定的。如果返回的是一个值(包括返回的是undefined、""),则第二个then走成功;如果返回的仍旧是一个promise对象,那么promise2会等待返回的这个promise对象的回调结果而确定promise2的状态值,如果回调结果拿到的是一个值(成功),那么promise2会将此值作为参数传入字节的reosolve中并执行,如果回调中抛出异常(失败),那么promise2会把异常传到reject中并且执行;

function Promise(fn){
    this.status = 'pending';
    this.onFulfilledCallbacks = [];
    this.onRejectedCallbacks =[]; 
    this.result = '';
    this.reason = '';
    var self = this;
    function resolve(data){
        if(self.status=='pending'){
            self.result = data;
            self.status = 'fulfilled';
            self.onFulfilledCallbacks.forEach((fn)=>{
                fn(data);
            })
        }
          
    }
    function reject(err){           
        if(self.status=='pending'){
          self.reason= err;
          self.status ='rejected';
          self.onRejectedCallbacks.forEach((fn)=>{
              fn(err);
          })
        }
    }
    try{
        fn(resolve,reject)
    }catch(e){
        reject(e)
    }
    
}
Promise.prototype.then= function(onFulfilled,onRejected){
   //then方法什么都不传,也可以支持连续调用  
   onFulfilled = onFulfilled ?onFulfilled :function(data){ return data};
   onRejected =onFulfilled ? onFulfilled :function(err){throw new Error(err)}
   let self = this;
   let Promise2;//声明primise2
   if(this.status =='fulfilled'){
       Promise2 = new Promise(function(resolve,reject){
           //promise2的状态,决定下一个then方法中执行成功还是失败。
           //promise2的状态,是由第一个then的onFulfilled的返回值决定的。
           //当我们执行onFulfilled(我们通过then方法传进来的自己的函数)的时候,是同步操作,需要通过trycatch捕获异常,如果发现异常就直接走下一个then的reject失败回调。
           //promise官方文档规定,每一个resolve或是reject回调的执行必须保证是在异步中执行,所以我们强制加定时器,保证onFulfilled是异步执行的。
           setTimeOut(function(){
               try{
                   let x = onFulfilled(self.result);
                   //获取到返回值,需要去解析,从而判断出promise2应该走失败还是成功。    
                   resolvePromise(Promise2,x,resolve,reject)                                              
               }catch(e){
                  //执行reject,下一个then就会走失败
                   reject(e);
               }
           })                                                      
       })          
   }
   if(this.status =='rejected'){
     Promise2 = new Promise(function(resolve,reject){
       setTimeout(function(){
           try{
               let x = onRejected(self.reason);
               resolvePromise(Promise2,x,resolve,reject)
           }catch(e){
               reject(e)
           }
       })
     })
       
   }
   if(this.status == 'pending'){
   Promise2 = new Promise(function(resolve,reject){
        self.onFulfilledCallbacks.push(function(){
            setTimeout(function(){
                try{
                    let x =  onFulfilled(self.result);
                    resolvePromise(Promise2,x,resolve,reject);
                }catch (e){
                    reject(e)
                }
            })

        });
        self.onRejectedCallbacks.push(function(){
            setTimeout(function(){
                try {
                    let x =  onRejected(self.reason);
                    resolvePromise(Promise2,x,resolve,reject)
                }catch (e){
                    reject(e);
                }
            })

        });
    })
   }
   return Promise2;
}

function resolvePromise(promise2,x,resolve,reject){
    //此处如果相等会爆出类型错误;
    if(promise2 == x){
        reject(new TypeError('循环引用了'))
    }
    //如果x是对象或函数(引用类型的值),则需要进一步判断。(这块儿要想的多一些,因为x是开发人员写的函数返回的,第一个then中回调返回的)
    //若果x是一个普通值,则直接执行resolve,并且传给下个then的成功; 
    //如果返回的是一个promise对象,则promise2则会等待返回的promise对象执行完成,如果执行完成后,看这个promise走的成功还是失败,如果失败则抛出异常。如果成功则将获取的数据作为onFulfilled返回的结果,用于判断promise2走成功或者失败,因为返回的结果可能还是promise对象,所以用递归去执行,知道拿到数据或者异常。(递归)
    //判断是不是promise对象,通过有没有then方法
    //捕获异常是因为判断不严谨,存在then方法,可能也不是promise对象,调用它的then可能会报错。      
    let called =false;
    if(x!==null &&(typeof x =='object'|| typeof x =='function')){        
           try{
               let then =x.then;
               if(typeof then =='function'){
                   //promise对象
                   then.call(x,function(y){
                       if(called)return;
                       called = true;
                       resolvePromise(promise2,y,resolve,reject)
                   },function(err){
                       if(called)return;
                       called = true;
                       reject(err)
                   })
               
               }else{
                   //普通对象
                   resolve(x)
               }
           }catch(e){
              if(called)return;
              called = true;
              reject(e)
           }
    }else{
        resolve(x);
    }    
}

 到此,Promise的大部分特性都已经具备了。但是Promise对象还有一些其他的方法,可供调用,比如说catch方法,还有他的私有属性all 、race、defferd,如果前面的Promise封装懂了,那这些方法就so easy了,下面会根据这些方法的功能一一进行封装,
 

1.all方法处理 并发请求,同时获得结果。一个失败,则失败,都成功,才算成功.这个时候我们就想到前面我们写的计数器的用法。

 Promise.all([read('./1.txt'),read('./2.txt')]).then(res=>{console.log(res)})
 
 Promise.all = function(promiseArray){               
       return new Promise(function(resolve,reject){
           var result = [];
           var i=0;
           function processData(index,res){
               result[index] = res;
               if(++i==promiseArray.length){
                   resolve(result)
               } 
           }
           promiseArray.forEach((item,index)=>{
               item.then(res=>{processData(index,res)},reject)
           })
       })        
 };

2.race方法,Pomise.race,顾名思义“赛拍”,传入多个异步promise,只要有一个成功,则就成功,有一个失败则失败,后面也可跟then方法。

Promise.race = function(promiseArray){
    return new Promise(function(resolve,reject){
        promiseArray.forEach((item,index)=>{
            item.then(resolve,reject);
        })
    })
}
Promise.race([read('./1.txt'),read('./5.txt')]).then(res=>{console.log(res)},err=>{console.log(err)})

3.生成一个成功的promise,把传入的参数,传入到then的成功回调中,该方法返回一个promise

Promise.resolve=function(value){
    return new Promise(function(resolve,reject){
        //promise规范 resolve和reject函数必须是在异步回调中执行
        setTimeout(function(){
            resolve(value);
        })
    })
}
Promise.resolve('123').then(res=>{console.log(res)})

4.生成一个失败的promise,把传入的参数,传入到then的失败回调中。该方法返回一个promise

Promise.reject = function(err){
    return new Promise(function(resolve,reject){
        setTimeout(function(){
            reject(err);
        })

    })
}
Promise.reject('error').then(res=>{console.log(res)},err=>{console.log(err)})

5.catch托底捕获错误,这个方法是实例的共有方法,应该放到Promise的原型上,每一个 promise实例都可以调用.它支持一个参数,该参数是之前所有的then中,并没有失败回调,当发 生错误时,最后统一在catch中进行捕获

Promise.prototype.catch = function(calllback){
return this.then(null,callback)
}

6.很多人都用过jquery的deferrd对象,他和promise的deffer对象很类似。promise的deferred对象只是对promise进行了一次封装

Promise.defer = Promise.deferred=function(){
    var obj = {};
    obj.promise = new Promise(function(resolve,reject){
        obj.resolve = resolve;
        obj.reject = reject;
    })
    return obj;
}
    let fs = require('fs');
 function read2 (url){
    var deferr = Promise.deferred();
    fs.readFile('./1.txt','utf8',(err,res)=>{
        err?deferr.reject(err):deferr.resolve(res);
    })
     return deferr;
}
read2('./1.txt').then(data=>{console.log(data)})

至此,一个完整的Promise.js封装完成,当然最后是需要模块化导出的,我们采用CommonJS规范导出一个模块 采用

module.exports = Promise;
查看原文

赞 5 收藏 6 评论 0

给自己一个微笑 关注了用户 · 2018-03-21

SevenOutman @sevenoutman

有耐心,但气不过不讲道理的事。
GitHub 间歇活跃用户。
赛文奥特曼??

关注 6557

认证与成就

  • 获得 2 次点赞
  • 获得 1 枚徽章 获得 0 枚金徽章, 获得 0 枚银徽章, 获得 1 枚铜徽章

擅长技能
编辑

(゚∀゚ )
暂时没有

开源项目 & 著作
编辑

(゚∀゚ )
暂时没有

注册于 2018-03-21
个人主页被 166 人浏览