资讯专栏INFORMATION COLUMN

callback

tianren124 / 3417人阅读

摘要:前言入门阮一峰另类的实现同级别的另外一个函数。该事件系统允许代码定义应用程序的特定事件,该事件可以传递自定义参数,自定义参数包含订阅者所需要的值。其目的是避免订阅者和发布者产生依赖关系。状态转变不可逆。方法必须返回一个。

callback 前言

ECMAScript 6入门(阮一峰)

setInterval: 另类的callback实现

setInterval同级别的另外一个函数:setTimeout。

设置n秒后,有一定时间延时的,2ms左右;

最低时间为4ms,参考传送门

var d = new Date, count = 0, f, timer;
timer = setInterval(f = function (){
    if(new Date - d > 1000) {
        clearInterval(timer), console.log(count);
    }
    count++;
}, 0);

setTimeout中的错误使用try,catch不可捕获

try{
    setTimeout(function(){
        throw new Error("我不希望这个错误出现!")
    }, 1000);
} catch(e){
    console.log(e.message);
}
callback: 常用的javascript回调

通常作为参数进行传递

function getData(callback) {
    $.ajax({
        url: "",
        success: resp => {
            callback(resp);
        }
    });
}
getData(resp => {
    // write your code here
});

调用的时候,可以直接调用,还可以通过bind,call,apply指定当前作用域

function getData(callback) {
    $.ajax({
        url: "",
        success: resp => {
            callback(resp.data);
            callback.bind(null)(resp.data);
            callback.call(null, resp.data);
            callback.apply(null, resp.data);
        }
    });
}
getData((...resp) => {
    // write your code here
});
事件监听: 一般用作dom的事件绑定

1.js自定义事件监听:

let myEvents = new MyEvent();
myEvents.addEvents({
    once: () => {
        console.log("只会console一次");
        myEvents.removeEvent("once");
    },
    infinity: () => {
        console.log("每次点击,都会console");
    }
});

document.onclick = e => {
    myEvents.fireEvents(["once", "infinity"]);
}

2.DOM自定义事件

let elImage = document.getElementById("image");
$(elImage).addEvent("click", e => {
    e = e || window.event;
    let target = e.target || e.srcElement;

    // 元素节点 为1; 元素属性 为2
    if (target.nodeType === 1) {
        console.log(`点击类型:${e.type}`);
        $(target).fireEvent("console");
    }
})

2.1.nodeType:

2.2.DOM事件流:

发布/订阅: 消息通讯

1.实现一个消息发布

let subPub = new SubPub();
subPub.subscribe("getName", name => {
    console.log("your name is: ", name);
});
subPub.publish("getName", "Tom");

1.观察者模式和发布/订阅的区别:

1.1.Observer模式要求希望接收到主题通知者的观察者必须订阅内容改变的事件

1.2.Subscribe/Publish模式使用了一个主题/事件通道,这个通道介于订阅者和发布者之间。该事件系统允许代码定义应用程序的特定事件,该事件可以传递自定义参数,自定义参数包含订阅者所需要的值。其目的是避免订阅者和发布者产生依赖关系。

from: 《Javascript设计模式》

2.nodejs版本的消息发布、订阅

const EventEmitter = require("events");

class MyEmitter extends EventEmitter {}

const myEmitter = new MyEmitter();
myEmitter.on("event", (a, b) => {
  console.log(a, b, this);
});
myEmitter.emit("event", "a", "b");

2.1.ES6中import对于循环引用的处理问题

TODO: require引用?

2.2.?commonJS中require是值的copy?,ES6中import是值的引用

3.更高级的状态管理:redux,vuex

promise: 回调的代码组织的封装 1.promise A+规范: wiki、plus、A+翻译 2.promise的流程

3.要点

3.1.Promise 本质是一个状态机。每个 promise 只能是 3 种状态中的一种:pending、fulfilled 或 rejected。状态转变只能是 pending -> fulfilled 或者 pending -> rejected。状态转变不可逆。

3.2.then 方法可以被同一个 promise 调用多次。

3.3.then 方法必须返回一个 promise。

4.一些问题

4.1.下面四个使用promise语句的不同点在哪里?

doSomething().then(function () {
    return doSomethingElse();
}).then(finalHandler);

doSomething().then(function () {
    doSomethingElse();
}).then(finalHandler);

doSomething().then(doSomethingElse()).then(finalHandler);

doSomething().then(doSomethingElse).then(finalHandler);

4.2.新手问题:

4.2.1.callback方式使用promise

// anotherPromise依赖somePromise
// 不推荐
somePromise()
.then(data => {
  anotherPromise(data.id)
  .then(anotherData => {
    // write your code here
  })
  .catch(window.console.log.bind(window.console))
})
.catch(window.console.log.bind(window.console))

// 推荐
somePromise()
.then(data => {
  return anotherPromise(data.id).then(data, anotherData);
})
then((data, another) => {

})
.catch(window.console.log.bind(window.console))

4.2.2.forEach使用promise,应该使用Promise.all

// 不推荐
let promises = [new Promise(resolve => {
  let dataA = {
    name: "dataA"
  };
  resolve(dataA);
}), new Promise(resolve => {
  let dataB = {
    name: "dataB"
  };
  resolve(dataB);
})];
let keys = ["dataA", "dataB"]
let dataAll = {};
promises.forEach((promise, index) => {
  promise
  .then(data => {
    dataAll[keys[index]] = data;
  })
  .catch(e => {
    console.log("error: ", e);
  })
});
// 推荐
Promise
.all(promises)
.then(data => {
  // [dataA, dataB]
})

4.2.3.忘记加catch

somePromise()
.then(() => {
  return anotherPromise();
})
.then(() => {
  return lastPromise();
})
// 没有业务错误需求,加上这句就方便调试
.catch(console.log.bind(console));

4.2.3.不推荐使用deferred(历史包袱),两种方式改正

4.2.3.1.使用第三方的库包装成promise,如angular的$q库:

$q.when(db.put(doc)).then(...)

4.2.3.2.使用promise:

new Promise(function (resolve, reject) {
    fs.readFile("myfile.txt", function (err, file) {
        if (err) {
            return reject(err);
        }
        resolve(file);
    });
})
.then(...)

4.2.4.不显示调用return

somePromise()
.then(() => {
  anotherPromise();
})
.then(data => {
  // data was undefined
})

4.3.进阶错误

4.3.1.不了解Promise.resolve()/Promise.reject();

4.3.2.catch和then(null, reject => {})不完全相同: then中的rejectHandler不会捕获resolveHandler中的错误

// 1.then reject
somePromise().then(resolve => {
  throw new Error("error");
}, reject => {
  // catch nothing
})
// 2.catch: this type was recomended
somePromise()
.then(resolve => {
  throw new Error("error");
})
.catch(e => {
  // catch the error
})

// 3.the same as below:
somePromise()
.then(resolve => {
  throw new Error("error");
})
.then(null, e => {
  // catch the error
})

4.3.3.promise vs promise factories: 一个接一个执行一系列的promise

function executeSequentially(promiseFactories) {
  var result = Promise.resolve();
  promiseFactories.forEach(function (promiseFactory) {
    result = result.then(promiseFactory);
  });
  return result;
}
// 使用promise工厂
function myPromiseFactory() {
  return somethingThatCreatesAPromise();
}
// 示例:
let promiseFactories = [];
promiseFactories.push(myPromiseFactory);
executeSequentially(promiseFactories);

4.3.4.想要两个promise的结果

4.3.4.1.原始代码

let getUserAndAccount = user => {
  return new Promise((resolve, reject) => {
    getUserAccountById(user.id)
    .then(userAccount => {
      resolve(user, userAccount);
    })
    .catch(reject);
  })
}
getUserByName("nolan")
.then(getUserAndAccount)
.then(function (user, userAccount) {
  console.log("user and userAccount: ", user, userAccount);
})
.cath(e => {
  console.log("error: ", e);
});

4.3.4.2.简化后代码

let getUserAndAccount = user => getUserAccountById(user.id)
                                .then(userAccount => Promise.resolve(user, userAccount))
getUserByName("nolan")
.then(getUserAndAccount)
.then(function (user, userAccount) {
  console.log("user and userAccount: ", user, userAccount);
})
.cath(e => {
  console.log("error: ", e);
});

4.3.5.值穿透

Promise.resolve("foo").then(Promise.resolve("bar")).then(function (result) {
  console.log(result);
});

4.3.6.不能cancel?,issue70, proposal-cancelable-promises

5.一些提议

5.1.then方法内部相关:

5.1.1.return一个promise对象。

5.1.2.return一个同步值或者是undefined

5.1.3.同步的throw一个错误

getUserByName("nolan").then(function (user) {
  if (user.isLoggedOut()) {
    throw new Error("user logged out!"); // throwing a synchronous error!
  }
  return inMemoryCache[user.id] || getUserAccountById(user.id);    // returning a synchronous value or a promise!
}).then(function (userAccount) {
  // I got a user account!
}).catch(function (err) {
  // Boo, I got an error!
  if (err) {
    let message = err.message;
    if (~message.indexOf("logged")) {
      // 已经登出的处理逻辑
    } else {
      // 其他的错误处理逻辑
    }
  }
});
6.一些Promise知识点

6.1.Promise.all, Promise.race

6.1.1.相同点: Promise.race和Promise.all都能接收一个数组

6.1.2.不同点: Promise.race只要有一个reject或者resolve,就立即返回,Promise.all等待所有的resolve,reject,才会返回,如果有一个reject,那么all的结果也是reject的(所有的resolve,才会resolve)

Promise.all([new Promise((resolve, reject) => {
  setTimeout(() => {
    console.log("first");
  }, 1000);
}), Promise.reject(123), new Promise((resolve, reject) => {
  console.log("second");
  resolve();
})])
.then(data => {
  console.log("I am all data: ", data);
})
.catch(e => {
  console.log("error", e);
});

6.1.3.使用场景: Promise.race可以在ajax网络超时判断使用

let timeout = 3e3;
Promise.race([new Promise((resolve, reject) => {
  $.ajax("url", resp => {
    console.log("ajax resp: ", resp);
  });
}), new Promise((resolve, reject) => {
  setTimeout(resolve, timeout);
})]);

6.2.Promise.resolve返回一个已经resolve的promise对象,reject同理

generator,yeild: 流程控制的新语法 1.generator的含义与定义: 异步操作的容器
function* gen(){
    let url = "https://api.github.com/users/github";
    let result = yield fetch(url);
    console.log("result: ", result.bio);
}

let genUser = () => {
    let g = gen();
    let result = g.next();

    result.value.then(data => {
        let json = data.json();
        return json;
    }).then(data => {
        g.next(data);
    });
}

1.1.函数可以暂停执行和恢复执行

1.2.Generator 函数可以不用yield表达式,这时就变成了一个单纯的暂缓执行函数

function* f() {
    console.log("执行了!")
}

var generator = f();

setTimeout(function () {
    generator.next()
}, 2000);

1.3.函数体内外的数据交换和

function* gen(x){
  var y = yield x + 2;
  console.log("gen(): ", y, x);
  return y;
}
var g = gen(1);
var value = g.next();
console.log("value: ", value);
var value2 = g.next(12);
console.log("value2: ", value2);

1.4.错误处理机制

function* gen(x){
  var y;
  try{
    y = yield x + 2;
    console.log("gen(): ", y, x);
  }catch(e){
    console.log(e);
  }
  return y;
}
var g = gen(1);
var value = g.next();
console.log("value: ", value);
var value2 = g.throw(new Error("error"));

1.5.yield表达式只能用在 Generator 函数里面

function f(param) {
    let a = yield 3 * param;
}
2.Thunk函数的含义与定义: 可以在回调函数里,将执行权交还给 Generator 函数,生产环境推荐thunkify
var gen = function* (){
  var f1 = yield readFile("fileA");
  var f2 = yield readFile("fileB");
  // ...
  var fn = yield readFile("fileN");
};

run(gen);

2.thunk函数介绍: 诞生于上个60年代

2.1.1.传值调用

let f = (a, b) => b;

f(3 * x * x - 2 * x - 1, x);

2.1.2.传名调用

let f = m => m * 2;

f(x + 5);

// 等同于

let thunk () => (x + 5);

let f = thunk => (thunk() * 2);

2.1.3.thunkify源码:

function thunkify(fn){
  return function(){
    let args = Array.prototype.slice.call(arguments);
    let ctx = this;

    return function(done){
      // 检查机制: 确保回调函数只运行一次
      let called;

      args.push(function(){
        if (called) return;
        called = true;
        done.apply(null, arguments);
      });

      try {
        fn.apply(ctx, args);
      } catch (err) {
        done(err);
      }
    }
  }
};

2.1.4.thunk与generator结合:

let fs = require("fs");
let thunkify = require("thunkify");
let readFile = thunkify(fs.readFile);

let gen = function* (){
  let r1 = yield readFile("/etc/fstab");
  console.log(r1.toString());
  let r2 = yield readFile("/etc/shells");
  console.log(r2.toString());
};

2.1.5.手动执行:

let g = gen();

let r1 = g.next();
r1.value(function(err, data){
  if (err) throw err;
  let r2 = g.next(data);
  r2.value(function(err, data){
    if (err) throw err;
    g.next(data);
  });
});

2.1.6.简化封装:

function run(fn) {
  let gen = fn();

  function next(err, data) {
    let result = gen.next(data);
    if (result.done) return;
    result.value(next);
  }

  next();
}

run(gen);
3.co函数库的含义与定义: Generator 函数的执行器, yield后必须是thunk/promise函数
var gen = function* (){
  var f1 = yield readFile("/etc/fstab");
  var f2 = yield readFile("/etc/shells");
  console.log(f1.toString());
  console.log(f2.toString());
};

var co = require("co");
co(gen);

3.1.协程与事件循环: 控制流的主动让出和恢复

3.1.1.提出时间: 1963; 提出人: Melvin Conway

3.1.2.历程: 进程->线程->用户态线程->协程

3.1.3.名词释义:

3.1.3.1.进程: 代码,被代码控制的资源(内存,I/O,文件)两大基本元素等组成的实体,两大特性[掌控资源,可以被调度]

3.1.3.2.线程: 程在进程内部,处理并发的逻辑,拥有独立的栈,却共享线程的资源

3.1.3.3.用户态线程: 线程切换的时候,进程需要为了管理而切换到内核态,处理状态转换(性能消耗严重)

3.1.4.没火的原因: 命令式编程(自顶向下开发,子历程作为唯一控制结构)、函数式编程[意气之争]

3.1.5.关系: 子历程是没有使用yield的协程。Donald Ervin Knuth(wiki)/Donald Ervin Knuth(baike): 子历程是协程的一种特例

3.2.使用co, yield后面放的必须是thunk/promise函数

async,await: generator的语法糖 async的含义与定义
let getData = () => {
    return new Promise((resolve, reject) => {
        $.ajax({
            url: "json/test.json",
            method: "GET",
            success: function (resp) {
                // data = resp.data;
                resolve(resp);
            },
            error: function (error) {
                reject(error);
            }
        });
    });
}

async function initView(){
    try {
        let resp = await getData();
        console.log(resp);
    } catch (e) {
        console.error(e);
    }
}
initView();
async的一些问题

1.同时触发:

// 写法一
let [foo, bar] = await Promise.all([getFoo(), getBar()]);

// 写法二
let fooPromise = getFoo();
let barPromise = getBar();
let foo = await fooPromise;
let bar = await barPromise;
最后的一些问题与思考

1.从异步操作上,async是最后演化的结果,callback是就不用了、还是应该尽量避免?

参考资料

Node.js最新技术栈之Promise篇

Promise实现原理

详解ES6 中的Promise与异步编程

深入Promise

你可能不知道的Promise

谈谈使用promise时候的一些反模式(EFE)

Promise Demo Implement

Promise Demo Implement for Question

JavaScript Promise迷你书(中文版)

mdn Promise

JavaScript Promises ... In Wicked Detail

JavaScript异步编程原理

深入掌握ECMAScript 6 异步编程系列(阮一峰)

漫谈js自定义事件、DOM/伪DOM自定义事件(张鑫旭)

js原生创建模拟事件和自定义事件

JS观察者模式

NodeJS Event

NodeJS EventEmitter

JS发布/订阅简单实现

扩展阅读

JS函数式编程指南

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/83679.html

相关文章

  • jQuery 源码系列(七)Callbacks 函数

    摘要:的支持的方法有几个主要的,和,比如官方有一个例子这两个作为函数调用的生成从基本可以看出,函数生成了一个对象,这个对象的方法是添加回调函数,而方法则是执行回调函数。 欢迎来我的专栏查看系列文章。 讲真,Sizzle 的源码真的太压抑了,以至于写 Sizzle 文章的这段时间里都非常的痛苦,刚开始觉得它还挺有意思的,越到后面越觉得代码很难读懂,烦。 寒假也过完了,在家里待了两周的时间,感觉...

    timger 评论0 收藏0
  • promise源码解析

    摘要:源码参考主要内容的迭代设计中主要的代码片段,翻译一部分加上自己的理解,同时指出的一些特性。先贴完整代码安全性和稳定性保证和在未来他们被调用的时候,应该是和注册时的顺序是保持一致的。这将显著降低异步编程中流程控制出错可能性。 源码参考https://github.com/kriskowal/...主要内容:promise的迭代设计中主要的代码片段,翻译一部分加上自己的理解,同时指出pro...

    kamushin233 评论0 收藏0
  • 带你彻底弄懂Event Loop

    前言 我在学习浏览器和NodeJS的Event Loop时看了大量的文章,那些文章都写的很好,但是往往是每篇文章有那么几个关键的点,很多篇文章凑在一起综合来看,才可以对这些概念有较为深入的理解。 于是,我在看了大量文章之后,想要写这么一篇博客,不采用官方的描述,结合自己的理解以及示例代码,用最通俗的语言表达出来。希望大家可以通过这篇文章,了解到Event Loop到底是一种什么机制,浏览器和Nod...

    hersion 评论0 收藏0
  • 二叉树遍历

    摘要:前言本篇文章是在二叉排序树的基础上进行遍历查找与删除结点。接下来我们根据构造的这颗二叉树进行相应遍历查找与删除操作。遍历二叉树二叉树的遍历分为深度优先遍历和广度优先遍历。中序遍历二叉排序树,得到的数组是有序的且是升序的。 前言 本篇文章是在二叉排序树的基础上进行遍历、查找、与删除结点。 那么首先来看一下什么是二叉排序树? 二叉排序树 定义 二叉排序树,又称二叉查找树、二叉搜索树。 若...

    aboutU 评论0 收藏0
  • 带你读Backbone源码解读之Events实现

    摘要:接受个参数,包括事件的名称,回调函数和回调函数执行的上下文环境。保留回调函数在数组中取出对应的以及中的函数。当然,你同样可以在绑定的回调函数执行前手动通过将其移除。 Backbone源码解读 Backbone在流行的前端框架中是最轻量级的一个,全部代码实现一共只有1831行1。从前端的入门再到Titanium,我虽然几次和Backbone打交道但是却对它的结构知之甚少,也促成了我想读...

    AndroidTraveler 评论0 收藏0
  • tornado 源码阅读-初步认识

    摘要:序言最近闲暇无事阅读了一下的源码对整体的结构有了初步认识与大家分享不知道为什么右边的目录一直出不来非常不舒服不如移步到吧是的核心模块也是个调度模块各种异步事件都是由他调度的所以必须弄清他的执行逻辑源码分析而的核心部分则是这个循环内部的逻辑贴 序言 最近闲暇无事,阅读了一下tornado的源码,对整体的结构有了初步认识,与大家分享 不知道为什么右边的目录一直出不来,非常不舒服. 不如移...

    2450184176 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<